stream_bluray: rework fill_buffer around bd_read_ext

Menu support added bd_read_ext for events but kept the old bd_read
path, leaving two read loops. Use bd_read_ext everywhere and handle
all events in one place.
This commit is contained in:
Kacper Michajłow committed 2026-08-26 01:47:41 +02:00
1 parent 4084e7609f
commit af53a9dc4b
1 file changed
+233 -176
+233 -176
View File
@@ -48,7 +48,6 @@
#include "osdep/threads.h"
#include "stream.h"
#include "osdep/io.h"
#include "osdep/timer.h"
#include "sub/osd.h"
#include "sub/img_convert.h"
#include "video/csputils.h"
@@ -67,6 +66,11 @@
#define BD_TIME_TO_MP(x) ((x) / (double)(BD_TIMEBASE))
#define BD_TIME_FROM_MP(x) ((uint64_t)(x * BD_TIMEBASE))
// Interval between read retries while navigation is idle
#define BLURAY_POLL_TIME_S 0.010
// In menu mode, tolerate this many consecutive idle reads at the end of playlist
#define BLURAY_NAV_EOF_POLLS 100
// copied from aacs.h in libaacs
#define AACS_ERROR_CORRUPTED_DISC -1 /* opening or reading of AACS files failed */
#define AACS_ERROR_NO_CONFIG -2 /* missing config file */
@@ -456,191 +460,244 @@ static void bluray_stream_close(stream_t *s)
mp_mutex_destroy(&priv->overlay_lock);
}
static void handle_event(stream_t *s, const BD_EVENT *ev)
static const char *bd_event_str(uint32_t event)
{
struct bluray_priv_s *b = s->priv;
if (b->hdmv_mode)
MP_VERBOSE(s, "bdnav: event %d param %u\n", ev->event, ev->param);
switch (ev->event) {
case BD_EVENT_MENU:
// ev->param: 1 if the disc is currently in an HDMV menu, 0 otherwise.
if (b->hdmv_mode) {
mp_mutex_lock(&b->overlay_lock);
b->menu_event_active = ev->param != 0;
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
}
break;
case BD_EVENT_STILL:
mp_mutex_lock(&b->overlay_lock);
b->still_active = ev->param != 0;
mp_mutex_unlock(&b->overlay_lock);
break;
case BD_EVENT_STILL_TIME:
if (ev->param == 0) {
mp_mutex_lock(&b->overlay_lock);
b->still_active = true;
mp_mutex_unlock(&b->overlay_lock);
} else {
bd_read_skip_still(b->bd);
}
break;
case BD_EVENT_END_OF_TITLE:
mp_mutex_lock(&b->overlay_lock);
b->still_active = false;
mp_mutex_unlock(&b->overlay_lock);
break;
case BD_EVENT_PLAYLIST: {
int playlist = ev->param;
int title = bd_get_current_title(b->bd);
if (b->title_to_playlist) {
for (int i = 0; i < b->num_titles; i++) {
if (b->title_to_playlist[i] == (uint32_t)ev->param) {
title = i;
break;
}
}
}
mp_mutex_lock(&b->overlay_lock);
int cur_angle = b->current_angle;
mp_mutex_unlock(&b->overlay_lock);
BLURAY_TITLE_INFO *ti = bd_get_playlist_info(b->bd, playlist, cur_angle);
mp_mutex_lock(&b->overlay_lock);
if (b->title_info)
bd_free_title_info(b->title_info);
b->title_info = ti;
b->current_playlist = playlist;
b->current_title = title;
if (b->hdmv_mode)
b->discontinuity_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
}
case BD_EVENT_TITLE: {
int title = bd_get_current_title(b->bd);
mp_mutex_lock(&b->overlay_lock);
if (b->title_info) {
bd_free_title_info(b->title_info);
b->title_info = NULL;
}
b->current_title = title;
if (b->hdmv_mode)
b->discontinuity_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
}
case BD_EVENT_ANGLE: {
int angle = MPMAX(0, (int)ev->param - 1);
BLURAY_TITLE_INFO *ti = b->title_info ? bd_get_playlist_info(b->bd, b->current_playlist, angle)
: NULL;
mp_mutex_lock(&b->overlay_lock);
b->current_angle = angle;
if (ti) {
bd_free_title_info(b->title_info);
b->title_info = ti;
}
if (b->hdmv_mode)
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
}
case BD_EVENT_AUDIO_STREAM:
mp_mutex_lock(&b->overlay_lock);
b->audio_stream_num = ev->param;
if (b->hdmv_mode)
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
case BD_EVENT_PG_TEXTST_STREAM:
mp_mutex_lock(&b->overlay_lock);
b->sub_stream_num = ev->param;
if (b->hdmv_mode)
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
case BD_EVENT_PG_TEXTST:
mp_mutex_lock(&b->overlay_lock);
b->sub_visible = ev->param != 0;
if (b->hdmv_mode)
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
case BD_EVENT_POPUP:
// ev->param: 1 if popup menu is currently available, 0 otherwise.
if (b->hdmv_mode) {
mp_mutex_lock(&b->overlay_lock);
b->popup_supported = ev->param != 0;
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
}
break;
#if BLURAY_VERSION >= BLURAY_VERSION_CODE(0, 5, 0)
case BD_EVENT_DISCONTINUITY:
break;
#if BLURAY_VERSION >= BLURAY_VERSION_CODE(1, 3, 0)
const char *name = bd_event_name(event);
if (name)
return name;
#endif
default:
MP_TRACE(s, "Unhandled event: %d %d\n", ev->event, ev->param);
break;
}
return "?";
}
static int bluray_stream_fill_buffer(stream_t *s, void *buf, int len)
{
struct bluray_priv_s *b = s->priv;
BD_EVENT event;
if (b->hdmv_mode) {
// bd_read() doesn't drive the HDMV VM, so the disc's first-play
// bytecode would never run and we'd be stuck with "no valid title"
// forever. bd_read_ext() runs the VM between event drains and also
// delivers one event per call, which we hand off to handle_event.
while (bd_get_event(b->bd, &event))
handle_event(s, &event);
if (b->still_active)
return 0;
int total = 0;
int events_seen = 0;
// Loop briefly to absorb event-only returns (where bd_read_ext
// returns 0 with a freshly produced event) before reporting EOF.
// If an event bumps discontinuity_id (PLAYLIST/TITLE) *after* we
// have already delivered data to the slave demuxer, stop here even
// if no data was read: the next bd_read_ext would deliver data from
// the new playlist, but the slave must be reopened first so it
// parses with the correct codec context.
for (int i = 0; i < 200; i++) {
uint32_t disc_before = b->discontinuity_id;
int n = bd_read_ext(b->bd, (uint8_t *)buf + total, len - total, &event);
if (n < 0) {
MP_VERBOSE(s, "bdnav: bd_read_ext err iter=%d\n", i);
return -1;
}
if (event.event != BD_EVENT_NONE) {
handle_event(s, &event);
events_seen++;
}
if (n > 0) {
total += n;
b->still_active = false;
break;
}
if (b->still_active)
break;
if (b->data_delivered && b->discontinuity_id != disc_before)
break;
if (mp_cancel_test(s->cancel))
return 0;
mp_sleep_ns(MP_TIME_MS_TO_NS(5));
mp_mutex_lock(&b->overlay_lock);
uint32_t disc_id = b->discontinuity_id;
mp_mutex_unlock(&b->overlay_lock);
int idle_reads = 0;
bool hopped = false;
while (!mp_cancel_test(s->cancel)) {
BD_EVENT ev;
// Once a jump was detected, stop consuming data (it belongs to the
// new position and the slave demuxer must be reopened first); a
// zero-length read only polls the remaining events.
int n = bd_read_ext(b->bd, buf, hopped ? 0 : len, &ev);
if (n < 0) {
MP_VERBOSE(s, "bd_read_ext() failed.\n");
return -1;
}
if (total > 0)
b->data_delivered = true;
if (total == 0)
MP_VERBOSE(s, "bdnav: fill returned 0 (events=%d)\n", events_seen);
return total;
}
while (bd_get_event(b->bd, &event))
handle_event(s, &event);
return bd_read(b->bd, buf, len);
if (ev.event != BD_EVENT_NONE)
MP_DBG(s, "event %s(%u) param %u\n", bd_event_str(ev.event), ev.event, ev.param);
bool stalled = false; // EOF unless the VM continues
bool bdj_idle = false; // BD-J title alive with no playlist playing
switch (ev.event) {
case BD_EVENT_NONE:
stalled = n == 0;
break;
case BD_EVENT_ERROR:
MP_ERR(s, "Blu-ray navigation error (%u).\n", ev.param);
return -1;
case BD_EVENT_READ_ERROR:
MP_WARN(s, "Blu-ray read error, skipping unit.\n");
break;
case BD_EVENT_END_OF_TITLE:
mp_mutex_lock(&b->overlay_lock);
b->still_active = false;
mp_mutex_unlock(&b->overlay_lock);
stalled = true;
break;
case BD_EVENT_IDLE:
bdj_idle = true;
break;
case BD_EVENT_STILL:
mp_mutex_lock(&b->overlay_lock);
b->still_active = ev.param != 0;
mp_mutex_unlock(&b->overlay_lock);
break;
case BD_EVENT_STILL_TIME:
// TODO: consider timed stills support
// param == 0 is an indefinite still
if (ev.param == 0) {
mp_mutex_lock(&b->overlay_lock);
b->still_active = true;
mp_mutex_unlock(&b->overlay_lock);
} else {
bd_read_skip_still(b->bd);
}
break;
case BD_EVENT_MENU:
// ev.param: 1 if the disc is currently in an HDMV menu, 0 otherwise.
if (b->hdmv_mode) {
mp_mutex_lock(&b->overlay_lock);
b->menu_event_active = ev.param != 0;
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
}
break;
case BD_EVENT_POPUP:
// ev.param: 1 if popup menu is currently available, 0 otherwise.
if (b->hdmv_mode) {
mp_mutex_lock(&b->overlay_lock);
b->popup_supported = ev.param != 0;
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
}
break;
case BD_EVENT_PLAYLIST: {
int playlist = ev.param;
int title = bd_get_current_title(b->bd);
if (b->title_to_playlist) {
for (int i = 0; i < b->num_titles; i++) {
if (b->title_to_playlist[i] == ev.param) {
title = i;
break;
}
}
}
mp_mutex_lock(&b->overlay_lock);
int cur_angle = b->current_angle;
mp_mutex_unlock(&b->overlay_lock);
BLURAY_TITLE_INFO *ti = bd_get_playlist_info(b->bd, playlist, cur_angle);
mp_mutex_lock(&b->overlay_lock);
if (b->title_info)
bd_free_title_info(b->title_info);
b->title_info = ti;
b->current_playlist = playlist;
b->current_title = title;
if (b->hdmv_mode)
b->discontinuity_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
}
case BD_EVENT_TITLE: {
int title = bd_get_current_title(b->bd);
mp_mutex_lock(&b->overlay_lock);
if (b->title_info) {
bd_free_title_info(b->title_info);
b->title_info = NULL;
}
b->current_title = title;
if (b->hdmv_mode)
b->discontinuity_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
}
case BD_EVENT_ANGLE: {
// The event value is 1-based, current_angle is 0-based.
int angle = MPMAX(0, (int)ev.param - 1);
BLURAY_TITLE_INFO *ti = b->title_info ? bd_get_playlist_info(b->bd, b->current_playlist, angle)
: NULL;
mp_mutex_lock(&b->overlay_lock);
b->current_angle = angle;
if (ti) {
bd_free_title_info(b->title_info);
b->title_info = ti;
}
if (b->hdmv_mode)
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
}
case BD_EVENT_AUDIO_STREAM:
mp_mutex_lock(&b->overlay_lock);
b->audio_stream_num = ev.param;
if (b->hdmv_mode)
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
case BD_EVENT_PG_TEXTST_STREAM:
mp_mutex_lock(&b->overlay_lock);
b->sub_stream_num = ev.param;
if (b->hdmv_mode)
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
case BD_EVENT_PG_TEXTST:
mp_mutex_lock(&b->overlay_lock);
b->sub_visible = ev.param != 0;
if (b->hdmv_mode)
b->nav_change_id++;
mp_mutex_unlock(&b->overlay_lock);
break;
case BD_EVENT_DISCONTINUITY:
break;
default:
MP_TRACE(s, "Unhandled event: %s(%u) %u\n",
bd_event_str(ev.event), ev.event, ev.param);
break;
}
if (n > 0) {
if (b->still_active) {
mp_mutex_lock(&b->overlay_lock);
b->still_active = false;
mp_mutex_unlock(&b->overlay_lock);
}
b->data_delivered = true;
return n;
}
mp_mutex_lock(&b->overlay_lock);
bool still_active = b->still_active;
if (!hopped && b->data_delivered && b->discontinuity_id != disc_id) {
MP_VERBOSE(s, "disc jumped (id %u -> %u), draining events\n",
disc_id, b->discontinuity_id);
hopped = true;
}
mp_mutex_unlock(&b->overlay_lock);
// The play position jumped to another title/playlist after data was
// already delivered: report EOF so demux_disc reopens the slave
// demuxer before it parses data from the new position. A single jump
// queues a burst of events (TITLE, PLAYLIST, ...) of which more than
// one can bump discontinuity_id, so hold the EOF until the burst is
// fully drained: with a bump still queued, the slave demuxer
// reopened for this jump would hit that bump's EOF right away while
// probing the new position, and fail to open.
if (hopped) {
if (ev.event != BD_EVENT_NONE)
continue;
MP_VERBOSE(s, "jump settled (id %u), EOF for demuxer resync\n",
b->discontinuity_id);
return 0;
}
// Holding an indefinite still frame: report EOF. The player keeps
// showing the last frame; user interaction releases the still and
// resumes reading through a discontinuity_id bump.
if (still_active) {
MP_VERBOSE(s, "holding still frame, EOF\n");
return 0;
}
if (bdj_idle) {
idle_reads = 0;
mp_cancel_wait(s->cancel, BLURAY_POLL_TIME_S);
} else if (stalled) {
// Without menus there is nothing that could continue: plain EOF.
if (!b->hdmv_mode)
return 0;
// Retry for a while before concluding that playback ended. The
// first retry is immediate: a just-resumed VM progresses at once.
if (++idle_reads > BLURAY_NAV_EOF_POLLS) {
MP_VERBOSE(s, "Navigation stopped, EOF.\n");
return 0;
}
if (idle_reads > 1)
mp_cancel_wait(s->cancel, BLURAY_POLL_TIME_S);
} else {
// The VM/title made progress; read again immediately.
idle_reads = 0;
}
}
return 0;
}
static int bluray_stream_control(stream_t *s, int cmd, void *arg)