Skip to content

Commit e14e08b

Browse files
committed
whisper : map token timestamps to original time when VAD is enabled
whisper_full_get_token_data().t0/t1 are in the VAD-processed timeline (silences removed), so they don't line up with the original audio. Add whisper_full_get_token_t0/t1 that map them back. A token inside a speech segment is interpolated within it; a token that falls in a removed inter-segment silence snaps to the nearest boundary, so it never lands in the middle of a cut-out gap. Without VAD the raw times are returned unchanged.
1 parent df7638d commit e14e08b

3 files changed

Lines changed: 86 additions & 0 deletions

File tree

include/whisper.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,16 @@ extern "C" {
667667
WHISPER_API whisper_token_data whisper_full_get_token_data (struct whisper_context * ctx, int i_segment, int i_token);
668668
WHISPER_API whisper_token_data whisper_full_get_token_data_from_state(struct whisper_state * state, int i_segment, int i_token);
669669

670+
// Get the start/end time of the specified token, in centiseconds. When VAD is enabled
671+
// these are mapped back to the original audio timeline (a token landing in a removed
672+
// inter-segment silence snaps to the nearest speech boundary), unlike
673+
// whisper_full_get_token_data().t0/t1 which stay in VAD-processed time. Without VAD the
674+
// raw token times are returned unchanged.
675+
WHISPER_API int64_t whisper_full_get_token_t0 (struct whisper_context * ctx, int i_segment, int i_token);
676+
WHISPER_API int64_t whisper_full_get_token_t0_from_state(struct whisper_state * state, int i_segment, int i_token);
677+
WHISPER_API int64_t whisper_full_get_token_t1 (struct whisper_context * ctx, int i_segment, int i_token);
678+
WHISPER_API int64_t whisper_full_get_token_t1_from_state(struct whisper_state * state, int i_segment, int i_token);
679+
670680
// Get the probability of the specified token in the specified segment
671681
WHISPER_API float whisper_full_get_token_p (struct whisper_context * ctx, int i_segment, int i_token);
672682
WHISPER_API float whisper_full_get_token_p_from_state(struct whisper_state * state, int i_segment, int i_token);

src/whisper.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8075,6 +8075,70 @@ struct whisper_token_data whisper_full_get_token_data(struct whisper_context * c
80758075
return ctx->state->result_all[i_segment].tokens[i_token];
80768076
}
80778077

8078+
// map a token time (centiseconds) from the VAD-processed timeline back to the original
8079+
// audio. a token inside a speech segment is interpolated within that segment; a token that
8080+
// falls in the silence removed between two segments snaps to the nearer boundary, so it
8081+
// never ends up in the middle of a cut-out gap (which a single global interpolation over
8082+
// the whole mapping table would do).
8083+
static int64_t whisper_map_token_time_segment_aware(
8084+
int64_t t,
8085+
const std::vector<whisper_state::vad_segment_info> & segs) {
8086+
if (segs.empty()) {
8087+
return t;
8088+
}
8089+
if (t <= segs.front().vad_start) {
8090+
return segs.front().orig_start;
8091+
}
8092+
if (t >= segs.back().vad_end) {
8093+
return segs.back().orig_end;
8094+
}
8095+
for (size_t i = 0; i < segs.size(); ++i) {
8096+
const auto & s = segs[i];
8097+
if (t >= s.vad_start && t <= s.vad_end) {
8098+
const int64_t vd = s.vad_end - s.vad_start;
8099+
const int64_t od = s.orig_end - s.orig_start;
8100+
if (vd <= 0) {
8101+
return s.orig_start;
8102+
}
8103+
return s.orig_start + (t - s.vad_start) * od / vd;
8104+
}
8105+
if (i + 1 < segs.size() && t > s.vad_end && t < segs[i + 1].vad_start) {
8106+
const int64_t mid = (s.vad_end + segs[i + 1].vad_start) / 2;
8107+
return (t <= mid) ? s.orig_end : segs[i + 1].orig_start;
8108+
}
8109+
}
8110+
return t;
8111+
}
8112+
8113+
int64_t whisper_full_get_token_t0_from_state(struct whisper_state * state, int i_segment, int i_token) {
8114+
const int64_t t0 = state->result_all[i_segment].tokens[i_token].t0;
8115+
if (!state->has_vad_segments || state->vad_segments.empty()) {
8116+
return t0;
8117+
}
8118+
return whisper_map_token_time_segment_aware(t0, state->vad_segments);
8119+
}
8120+
8121+
int64_t whisper_full_get_token_t0(struct whisper_context * ctx, int i_segment, int i_token) {
8122+
return whisper_full_get_token_t0_from_state(ctx->state, i_segment, i_token);
8123+
}
8124+
8125+
int64_t whisper_full_get_token_t1_from_state(struct whisper_state * state, int i_segment, int i_token) {
8126+
const int64_t t1 = state->result_all[i_segment].tokens[i_token].t1;
8127+
if (!state->has_vad_segments || state->vad_segments.empty()) {
8128+
return t1;
8129+
}
8130+
const int64_t orig_t0 = whisper_full_get_token_t0_from_state(state, i_segment, i_token);
8131+
int64_t orig_t1 = whisper_map_token_time_segment_aware(t1, state->vad_segments);
8132+
if (orig_t1 < orig_t0 + 1) {
8133+
orig_t1 = orig_t0 + 1; // keep a strictly positive duration after snapping
8134+
}
8135+
return orig_t1;
8136+
}
8137+
8138+
int64_t whisper_full_get_token_t1(struct whisper_context * ctx, int i_segment, int i_token) {
8139+
return whisper_full_get_token_t1_from_state(ctx->state, i_segment, i_token);
8140+
}
8141+
80788142
float whisper_full_get_token_p_from_state(struct whisper_state * state, int i_segment, int i_token) {
80798143
return state->result_all[i_segment].tokens[i_token].p;
80808144
}

tests/test-vad-full.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ int main() {
5050
assert(whisper_full_get_segment_t0(wctx, 0) == 32);
5151
assert(whisper_full_get_segment_t1(wctx, 0) == 1051);
5252

53+
// token times mapped back to the original timeline: ordered, non-negative duration
54+
const int n_tokens = whisper_full_n_tokens(wctx, 0);
55+
assert(n_tokens > 0);
56+
int64_t prev_t0 = -1;
57+
for (int j = 0; j < n_tokens; ++j) {
58+
const int64_t t0 = whisper_full_get_token_t0(wctx, 0, j);
59+
const int64_t t1 = whisper_full_get_token_t1(wctx, 0, j);
60+
assert(t0 >= 0 && t1 >= t0);
61+
assert(t0 >= prev_t0);
62+
prev_t0 = t0;
63+
}
64+
5365
whisper_free(wctx);
5466

5567
return 0;

0 commit comments

Comments
 (0)