Skip to main content

atmos/os_lib/
mse.rs

1//! W3C Media Source Extensions (MSE) 仕様準拠コアモジュール
2//!
3//! `MediaSource`、`SourceBuffer`、`SourceBufferList`、`TimeRanges` の状態機械および
4//! ストリーミング動画チャンク(fMP4 / WebM)のバッファリング管理を提供する。
5//! ハードウェア非依存の純粋ロジックであり、ホストテスト環境でも高速・安全に検証可能。
6
7extern crate alloc;
8
9use super::fmp4::{FragSample, Fmp4Demuxer, TrackKind};
10use alloc::string::String;
11use alloc::vec::Vec;
12
13/// MediaSource のライフサイクル状態
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum MediaSourceReadyState {
16    Closed,
17    Open,
18    Ended,
19}
20
21impl MediaSourceReadyState {
22    pub fn as_str(&self) -> &'static str {
23        match self {
24            Self::Closed => "closed",
25            Self::Open => "open",
26            Self::Ended => "ended",
27        }
28    }
29}
30
31/// 再生時間範囲(TimeRanges)
32#[derive(Debug, Clone, PartialEq)]
33pub struct TimeRange {
34    pub start: f64,
35    pub end: f64,
36}
37
38#[derive(Debug, Clone, PartialEq, Default)]
39pub struct TimeRanges {
40    pub ranges: Vec<TimeRange>,
41}
42
43impl TimeRanges {
44    pub fn new() -> Self {
45        Self { ranges: Vec::new() }
46    }
47
48    pub fn len(&self) -> usize {
49        self.ranges.len()
50    }
51
52    pub fn is_empty(&self) -> bool {
53        self.ranges.is_empty()
54    }
55
56    pub fn start(&self, index: usize) -> Option<f64> {
57        self.ranges.get(index).map(|r| r.start)
58    }
59
60    pub fn end(&self, index: usize) -> Option<f64> {
61        self.ranges.get(index).map(|r| r.end)
62    }
63
64    pub fn add(&mut self, start: f64, end: f64) {
65        if start >= end {
66            return;
67        }
68        self.ranges.push(TimeRange { start, end });
69    }
70
71    /// 2 つの TimeRanges の共通部分(`<video>.buffered` は全 SourceBuffer の共通部分。MSE 仕様)。
72    pub fn intersect(&self, other: &TimeRanges) -> TimeRanges {
73        let mut out = TimeRanges::new();
74        for a in &self.ranges {
75            for b in &other.ranges {
76                out.add(a.start.max(b.start), a.end.min(b.end));
77            }
78        }
79        out
80    }
81
82    /// 区間の集合から、重なり・`gap` 秒以下の隙間を併合した TimeRanges を作る(昇順)。
83    pub fn from_intervals(mut iv: Vec<(f64, f64)>, gap: f64) -> Self {
84        iv.retain(|(a, b)| a < b);
85        iv.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
86        let mut out = Self::new();
87        for (a, b) in iv {
88            match out.ranges.last_mut() {
89                Some(last) if a <= last.end + gap => last.end = last.end.max(b),
90                _ => out.ranges.push(TimeRange { start: a, end: b }),
91            }
92        }
93        out
94    }
95}
96
97/// MSE エラー
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum MseError {
100    NotOpen,
101    UnsupportedType,
102    AlreadyUpdating,
103    BufferNotFound,
104    /// 追加されたバイト列を解析できなかった(MSE の append error)。
105    ParseError,
106    /// 不正なプロパティ値(mode, timestampOffset, appendWindowStart/End)。
107    InvalidAccess,
108}
109
110/// 個別トラックの SourceBuffer データ。
111/// 【2026-10-17】従来はバイト列を貯めて「100KB = 1 秒」と見なした buffered を返す仮実装だった。
112/// fMP4 を `Fmp4Demuxer` で解析し、取り出したサンプルの表示時刻から buffered を作る。
113pub struct SourceBufferCore {
114    pub id: usize,
115    pub mime_type: String,
116    pub updating: bool,
117    pub buffered: TimeRanges,
118    /// `"segments"`(既定)または `"sequence"`。
119    pub mode: String,
120    /// セグメント内のタイムスタンプに加算するオフセット(秒)。
121    pub timestamp_offset: f64,
122    /// 取り込む表示時刻の下限(秒、既定 0.0)。
123    pub append_window_start: f64,
124    /// 取り込む表示時刻の上限(秒、既定 +∞)。
125    pub append_window_end: f64,
126    /// 解析済みのサンプル(復号順。トラック情報は `demux.tracks`)。
127    pub samples: Vec<FragSample>,
128    pub demux: Fmp4Demuxer,
129    /// 所属する MediaSource の番号(JS 側の `_ms_id`。0 は未所属)。
130    pub owner: usize,
131}
132
133impl SourceBufferCore {
134    pub fn new(id: usize, mime_type: &str) -> Self {
135        Self {
136            id,
137            mime_type: mime_type.into(),
138            updating: false,
139            buffered: TimeRanges::new(),
140            mode: String::from("segments"),
141            timestamp_offset: 0.0,
142            append_window_start: 0.0,
143            append_window_end: f64::INFINITY,
144            samples: Vec::new(),
145            demux: Fmp4Demuxer::new(),
146            owner: 0,
147        }
148    }
149
150    pub fn set_mode(&mut self, mode: &str) -> Result<(), MseError> {
151        if self.updating {
152            return Err(MseError::AlreadyUpdating);
153        }
154        if mode != "segments" && mode != "sequence" {
155            return Err(MseError::InvalidAccess);
156        }
157        self.mode = String::from(mode);
158        Ok(())
159    }
160
161    pub fn set_timestamp_offset(&mut self, offset: f64) -> Result<(), MseError> {
162        if self.updating {
163            return Err(MseError::AlreadyUpdating);
164        }
165        if !offset.is_finite() {
166            return Err(MseError::InvalidAccess);
167        }
168        self.timestamp_offset = offset;
169        Ok(())
170    }
171
172    pub fn set_append_window_start(&mut self, start: f64) -> Result<(), MseError> {
173        if self.updating {
174            return Err(MseError::AlreadyUpdating);
175        }
176        if !start.is_finite() || start < 0.0 || start >= self.append_window_end {
177            return Err(MseError::InvalidAccess);
178        }
179        self.append_window_start = start;
180        Ok(())
181    }
182
183    pub fn set_append_window_end(&mut self, end: f64) -> Result<(), MseError> {
184        if self.updating {
185            return Err(MseError::AlreadyUpdating);
186        }
187        if end.is_nan() || end <= self.append_window_start {
188            return Err(MseError::InvalidAccess);
189        }
190        self.append_window_end = end;
191        Ok(())
192    }
193
194    /// `changeType(type)`: 同一 SourceBuffer 上で後続セグメントの MIME タイプ・コーデックを切り替える。
195    pub fn change_type(&mut self, mime_type: &str) -> Result<(), MseError> {
196        if self.updating {
197            return Err(MseError::AlreadyUpdating);
198        }
199        if mime_type.trim().is_empty() || !MediaSourceCore::is_type_supported(mime_type) {
200            return Err(MseError::UnsupportedType);
201        }
202        self.mime_type = String::from(mime_type);
203        Ok(())
204    }
205
206    /// 初期化セグメント(moov)から得られた映像トラックの (width, height)。
207    pub fn video_dimensions(&self) -> Option<(u32, u32)> {
208        self.demux
209            .tracks
210            .iter()
211            .find(|t| t.kind == TrackKind::Video && t.width > 0 && t.height > 0)
212            .map(|t| (t.width, t.height))
213    }
214
215    /// チャンクの追記(appendBuffer)。初期化セグメントもメディアセグメントも、任意の区切りで渡せる。
216    pub fn append_buffer(&mut self, chunk: &[u8]) -> Result<(), MseError> {
217        if self.updating {
218            return Err(MseError::AlreadyUpdating);
219        }
220        self.updating = true;
221        let r = self.demux.push(chunk);
222        self.updating = false;
223        let new = r.map_err(|_| MseError::ParseError)?;
224        if !new.is_empty() {
225            let scales: Vec<(u32, f64)> = self
226                .demux
227                .tracks
228                .iter()
229                .map(|t| (t.track_id, t.timescale.max(1) as f64))
230                .collect();
231            let scale_of = |tid: u32| -> f64 {
232                scales.iter().find(|(id, _)| *id == tid).map_or(1000.0, |x| x.1)
233            };
234
235            let primary_tid = self
236                .demux
237                .tracks
238                .iter()
239                .find(|t| t.kind == TrackKind::Video)
240                .or_else(|| self.demux.tracks.first())
241                .map(|t| t.track_id);
242
243            // sequence モード: バッチ内の主トラック(無ければ全体)の最小 PTS が現在の timestamp_offset に揃うよう補正
244            let effective_offset = if self.mode == "sequence" {
245                let min_pts_sec = new
246                    .iter()
247                    .filter(|s| primary_tid.is_none_or(|pid| s.track_id == pid))
248                    .map(|s| s.pts as f64 / scale_of(s.track_id))
249                    .reduce(f64::min)
250                    .or_else(|| {
251                        new.iter()
252                            .map(|s| s.pts as f64 / scale_of(s.track_id))
253                            .reduce(f64::min)
254                    })
255                    .unwrap_or(0.0);
256                self.timestamp_offset - min_pts_sec
257            } else {
258                self.timestamp_offset
259            };
260
261            let mut max_end_sec = self.timestamp_offset;
262            let mut filtered = Vec::with_capacity(new.len());
263            for mut s in new {
264                let ts = scale_of(s.track_id);
265                let delta_ticks = libm::round(effective_offset * ts) as i64;
266                s.pts += delta_ticks;
267                s.dts = (s.dts as i64 + delta_ticks).max(0) as u64;
268                let pts_sec = s.pts as f64 / ts;
269                let end_sec = (s.pts + s.duration as i64) as f64 / ts;
270                if primary_tid.is_none_or(|pid| s.track_id == pid) && end_sec > max_end_sec {
271                    max_end_sec = end_sec;
272                }
273                // appendWindowStart / appendWindowEnd による Coded Frame Filtering
274                if pts_sec + 1e-6 >= self.append_window_start && end_sec - 1e-6 <= self.append_window_end {
275                    // 同一トラックで同じ表示時刻付近の既存フレームがあれば上書き(重複回避)
276                    self.samples.retain(|old| {
277                        !(old.track_id == s.track_id && (old.pts - s.pts).abs() * 2 < s.duration.max(1) as i64)
278                    });
279                    filtered.push(s);
280                }
281            }
282            if self.mode == "sequence" {
283                self.timestamp_offset = max_end_sec;
284            }
285            self.samples.extend(filtered);
286            self.update_buffered();
287        }
288        Ok(())
289    }
290
291    /// buffered(秒): 最初のトラック(映像があれば映像)のサンプルの表示区間を併合したもの。
292    fn update_buffered(&mut self) {
293        let track = self
294            .demux
295            .tracks
296            .iter()
297            .find(|t| t.kind == TrackKind::Video)
298            .or_else(|| self.demux.tracks.first());
299        let Some(t) = track else { return };
300        let (id, ts) = (t.track_id, t.timescale.max(1) as f64);
301        let iv = self
302            .samples
303            .iter()
304            .filter(|s| s.track_id == id)
305            .map(|s| (s.pts as f64 / ts, (s.pts as f64 + s.duration as f64) / ts))
306            .collect();
307        // フレーム間の丸め誤差程度の隙間は連続とみなす。
308        self.buffered = TimeRanges::from_intervals(iv, 0.05);
309    }
310
311    /// `remove(start, end)`: 表示時刻が [start, end) 秒のサンプルを捨てる。
312    pub fn remove_range(&mut self, start: f64, end: f64) {
313        let ts: Vec<(u32, f64)> = self.demux.tracks.iter().map(|t| (t.track_id, t.timescale.max(1) as f64)).collect();
314        self.samples.retain(|s| {
315            let scale = ts.iter().find(|(id, _)| *id == s.track_id).map_or(1000.0, |x| x.1);
316            let t = s.pts as f64 / scale;
317            !(t >= start && t < end)
318        });
319        self.update_buffered();
320    }
321
322    /// バッファのクリア(abort / remove)
323    pub fn abort(&mut self) {
324        self.updating = false;
325    }
326
327    pub fn clear(&mut self) {
328        self.samples.clear();
329        self.buffered.ranges.clear();
330        self.updating = false;
331    }
332}
333
334/// MediaSource の純粋コア実装
335pub struct MediaSourceCore {
336    pub ready_state: MediaSourceReadyState,
337    pub duration: f64,
338    pub source_buffers: Vec<SourceBufferCore>,
339    pub live_seekable_range: Option<(f64, f64)>,
340    next_buffer_id: usize,
341}
342
343impl MediaSourceCore {
344    pub fn new() -> Self {
345        Self {
346            ready_state: MediaSourceReadyState::Closed,
347            duration: 0.0,
348            source_buffers: Vec::new(),
349            live_seekable_range: None,
350            next_buffer_id: 1,
351        }
352    }
353
354    /// 対象の MIME タイプとコーデック・追加パラメータが実際に復号できるか。
355    /// 【2026-10-17】従来は WebM・MP3 も「対応」と答えていたが、VP8/VP9/AV1/Opus と MP3 の
356    /// デコーダは無い。YouTube のプレーヤーはここで選ぶ形式を決めるので、復号できるもの
357    /// (fMP4 の H.264 = avc1/avc3 と AAC-LC = mp4a.40.2 / mp4a.67)だけを返す。
358    /// 【2026-10-18】YouTube が画質・上限判定のために付与する追加パラメータ(`width=99999`、
359    /// `height=99999`、`framerate=999`、`channels=99`、`bitrate=999999999` 等)や未知パラメータも検証する。
360    pub fn is_type_supported(mime_type: &str) -> bool {
361        let lower = mime_type.to_ascii_lowercase();
362        let mut parts = lower.split(';');
363        let container = parts.next().unwrap_or("").trim();
364        if container != "video/mp4" && container != "audio/mp4" {
365            return false;
366        }
367        let mut codecs: Option<&str> = None;
368        for param in parts.map(str::trim).filter(|s| !s.is_empty()) {
369            let Some((k, v)) = param.split_once('=') else {
370                return false;
371            };
372            let k = k.trim();
373            let v = v.trim().trim_matches(|ch| ch == '"' || ch == '\'');
374            match k {
375                "codecs" => {
376                    if v.is_empty() {
377                        return false;
378                    }
379                    codecs = Some(v);
380                }
381                "width" => {
382                    let Ok(n) = v.parse::<u32>() else { return false };
383                    if !(1..=3840).contains(&n) {
384                        return false;
385                    }
386                }
387                "height" => {
388                    let Ok(n) = v.parse::<u32>() else { return false };
389                    if !(1..=2160).contains(&n) {
390                        return false;
391                    }
392                }
393                "framerate" => {
394                    let Ok(n) = v.parse::<f64>() else { return false };
395                    if !(n > 0.0 && n <= 120.0) {
396                        return false;
397                    }
398                }
399                "bitrate" => {
400                    let Ok(n) = v.parse::<u64>() else { return false };
401                    if !(1..=80_000_000).contains(&n) {
402                        return false;
403                    }
404                }
405                "channels" => {
406                    let Ok(n) = v.parse::<u32>() else { return false };
407                    if !(1..=8).contains(&n) {
408                        return false;
409                    }
410                }
411                "samplerate" => {
412                    let Ok(n) = v.parse::<u32>() else { return false };
413                    if !(8000..=96000).contains(&n) {
414                        return false;
415                    }
416                }
417                "cryptoblockformat" | "eotf" => {}
418                _ => return false,
419            }
420        }
421        let Some(codecs) = codecs else { return true };
422        codecs.split(',').map(str::trim).all(|c| {
423            !c.is_empty()
424                && (c.starts_with("avc1.")
425                    || c.starts_with("avc3.")
426                    || c == "avc1"
427                    || c == "mp4a.40.2"
428                    || c == "mp4a.67"
429                    || c == "mp4a")
430        })
431    }
432
433    /// ストリームオープン(URL.createObjectURL または video.src 割り当て時)
434    pub fn open(&mut self) {
435        self.ready_state = MediaSourceReadyState::Open;
436    }
437
438    /// SourceBuffer の追加(addSourceBuffer)
439    pub fn add_source_buffer(&mut self, mime_type: &str) -> Result<usize, MseError> {
440        if self.ready_state != MediaSourceReadyState::Open {
441            return Err(MseError::NotOpen);
442        }
443        if !Self::is_type_supported(mime_type) {
444            return Err(MseError::UnsupportedType);
445        }
446        let id = self.next_buffer_id;
447        self.next_buffer_id += 1;
448        self.source_buffers.push(SourceBufferCore::new(id, mime_type));
449        Ok(id)
450    }
451
452    /// SourceBuffer の削除(removeSourceBuffer)
453    pub fn remove_source_buffer(&mut self, id: usize) -> Result<(), MseError> {
454        if let Some(pos) = self.source_buffers.iter().position(|b| b.id == id) {
455            self.source_buffers.remove(pos);
456            Ok(())
457        } else {
458            Err(MseError::BufferNotFound)
459        }
460    }
461
462    /// SourceBuffer の取得
463    pub fn get_source_buffer_mut(&mut self, id: usize) -> Option<&mut SourceBufferCore> {
464        self.source_buffers.iter_mut().find(|b| b.id == id)
465    }
466
467    /// ストリームの終了(endOfStream)
468    pub fn end_of_stream(&mut self) -> Result<(), MseError> {
469        if self.ready_state != MediaSourceReadyState::Open {
470            return Err(MseError::NotOpen);
471        }
472        self.ready_state = MediaSourceReadyState::Ended;
473        Ok(())
474    }
475
476    /// ライブ配信用のシーク可能範囲を設定する(`setLiveSeekableRange(start, end)`)。
477    pub fn set_live_seekable_range(&mut self, start: f64, end: f64) -> Result<(), MseError> {
478        if self.ready_state != MediaSourceReadyState::Open {
479            return Err(MseError::NotOpen);
480        }
481        if !start.is_finite() || !end.is_finite() || start < 0.0 || start > end {
482            return Err(MseError::InvalidAccess);
483        }
484        self.live_seekable_range = Some((start, end));
485        Ok(())
486    }
487
488    /// ライブ配信用のシーク可能範囲を解除する(`clearLiveSeekableRange()`)。
489    pub fn clear_live_seekable_range(&mut self) -> Result<(), MseError> {
490        if self.ready_state != MediaSourceReadyState::Open {
491            return Err(MseError::NotOpen);
492        }
493        self.live_seekable_range = None;
494        Ok(())
495    }
496}
497
498impl Default for MediaSourceCore {
499    fn default() -> Self {
500        Self::new()
501    }
502}
503
504/// JS の `<video>.play()` / `.pause()` からホスト(描画ループ)への再生要求。
505/// 【2026-10-17】従来の `play()` は内部属性を書き換えるだけで、実際の再生は始まらなかった。
506#[derive(Debug, Clone, PartialEq)]
507pub enum MediaRequest {
508    /// 指定 URL(`blob:mediasource-…` を含む)を再生する。
509    Play(String),
510    Pause,
511}
512
513/// 最新の要求 1 つ(描画ループが `take` する)。
514pub static MEDIA_REQUEST: spin::Mutex<Option<MediaRequest>> = spin::Mutex::new(None);
515
516/// 再生スレッドが書き、JS(`currentTime`/`paused`/`ended`/`getVideoPlaybackQuality`)と
517/// 描画ループ(`timeupdate`/`ended` イベント)が読む再生状態。
518/// 【2026-10-17】従来の `currentTime` 等は内部属性を返すだけで、実際の再生と無関係だった。
519#[derive(Clone, Debug, PartialEq)]
520pub struct PlaybackStatus {
521    /// 再生中(または最後に再生した)`<video>` の src。
522    pub url: String,
523    /// 表示中のフレームの時刻(秒)。
524    pub current_time: f64,
525    pub playing: bool,
526    pub ended: bool,
527    /// 終了をまだ JS に知らせていない(描画ループが `ended` を発火したら false)。
528    pub ended_pending: bool,
529    /// バッファ待機状態(waiting)をまだ JS に知らせていない
530    pub waiting_pending: bool,
531    /// 再生開始・再開(playing)をまだ JS に知らせていない
532    pub playing_pending: bool,
533    /// データ取得停滞(stalled)をまだ JS に知らせていない
534    pub stalled_pending: bool,
535    /// デコード・表示した累計フレーム数(`getVideoPlaybackQuality().totalVideoFrames` 用)。
536    pub total_video_frames: u32,
537    /// ドロップまたはデコード失敗したフレーム数(`getVideoPlaybackQuality().droppedVideoFrames` 用)。
538    pub dropped_video_frames: u32,
539    /// 現在の音量(0.0〜1.0、既定 1.0)。
540    pub volume: f64,
541    /// ミュート状態(既定 false)。
542    pub muted: bool,
543    /// 再生速度(既定 1.0)。
544    pub playback_rate: f64,
545}
546
547impl PlaybackStatus {
548    pub const fn new() -> Self {
549        Self {
550            url: String::new(),
551            current_time: 0.0,
552            playing: false,
553            ended: false,
554            ended_pending: false,
555            waiting_pending: false,
556            playing_pending: false,
557            stalled_pending: false,
558            total_video_frames: 0,
559            dropped_video_frames: 0,
560            volume: 1.0,
561            muted: false,
562            playback_rate: 1.0,
563        }
564    }
565
566    /// 同じ URL で一時停止中(`!ended && current_time > 0.0`)から再開すべきかを返す。
567    pub fn should_resume(&self, url: &str) -> bool {
568        !url.is_empty() && self.url == url && !self.ended && self.current_time > 0.0
569    }
570
571    /// 音量を 0.0〜1.0 の範囲で設定する(範囲外や NaN は無視して false を返す)。
572    pub fn set_volume(&mut self, v: f64) -> bool {
573        if v.is_finite() && (0.0..=1.0).contains(&v) {
574            self.volume = v;
575            true
576        } else {
577            false
578        }
579    }
580
581    /// 再生速度を 0.25〜4.0 にクランプして設定する(0 以下や非有限は無視して false)。
582    pub fn set_playback_rate(&mut self, r: f64) -> bool {
583        if r.is_finite() && r > 0.0 {
584            self.playback_rate = r.clamp(0.25, 4.0);
585            true
586        } else {
587            false
588        }
589    }
590}
591
592pub static PLAYBACK: spin::Mutex<PlaybackStatus> = spin::Mutex::new(PlaybackStatus::new());
593
594/// `video.currentTime = t`(秒)によるシーク要求。再生スレッドが取り出す。
595pub static SEEK_REQUEST: spin::Mutex<Option<f64>> = spin::Mutex::new(None);
596
597/// 音声トラックのサンプル列から、`after_dts` より後かつ `pts_sec <= max_pts_sec` の範囲にある
598/// 最大 `max_frames` 個のサンプル payload と最後の `dts` を取り出す。
599/// 映像再生と並行して音声を小分けにストリーミング供給するために使う。
600pub fn select_audio_samples_in_window(
601    samples: &[FragSample],
602    track_id: u32,
603    timescale: u32,
604    after_dts: Option<u64>,
605    max_pts_sec: f64,
606    max_frames: usize,
607) -> (Vec<Vec<u8>>, Option<u64>) {
608    if max_frames == 0 || timescale == 0 {
609        return (Vec::new(), after_dts);
610    }
611    let ts = timescale as f64;
612    let mut out = Vec::new();
613    let mut last_dts = after_dts;
614    for s in samples {
615        if s.track_id != track_id {
616            continue;
617        }
618        if let Some(prev) = last_dts {
619            if s.dts <= prev {
620                continue;
621            }
622        }
623        let pts_sec = (s.pts.max(0) as f64) / ts;
624        if pts_sec > max_pts_sec {
625            break;
626        }
627        out.push(s.data.clone());
628        last_dts = Some(s.dts);
629        if out.len() >= max_frames {
630            break;
631        }
632    }
633    (out, last_dts)
634}
635
636/// デコード済み 16-bit PCM サンプル列に音量(0.0〜1.0)とミュート状態を適用する。
637pub fn scale_pcm_samples(pcm: &mut [i16], volume: f64, muted: bool) {
638    if muted || !volume.is_finite() || volume <= 0.0 {
639        pcm.fill(0);
640        return;
641    }
642    let vol = volume.clamp(0.0, 1.0);
643    if (vol - 1.0).abs() < 1e-6 {
644        return;
645    }
646    let scale = (vol * 256.0) as i32;
647    for sample in pcm.iter_mut() {
648        let v = (*sample as i32 * scale) >> 8;
649        *sample = v.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
650    }
651}
652