Feature/failover (#924)

* feature stream failover
This commit is contained in:
euzu
2026-10-16 21:10:07 +02:00
committed by GitHub
parent 86d19f955c
commit 0e4e01409c
203 changed files with 15664 additions and 820 deletions
@@ -1,12 +1,10 @@
use super::{
clock::{
add_pts_dts_offset, decode_pcr_at_location, ticks_90khz_to_rounded_millis, ts_chunk_packet_count,
TS_PACKET_SIZE,
},
clock::{add_pts_dts_offset, decode_pcr_at_location, ticks_90khz_to_rounded_millis, ts_chunk_packet_count},
layout::{build_hls_finite_ts_layout, find_ts_alignment, finite_hls_presentation_duration},
profile::timestamp_profile_from_finite_layout,
HlsTsTimestampProfile, TransportStreamBuffer,
};
use crate::packet::TS_PACKET_SIZE;
use bytes::{Bytes, BytesMut};
use futures::task::AtomicWaker;
use sha2::{Digest, Sha256};
@@ -200,10 +198,10 @@ impl TransportStreamBuffer {
let packet_layout =
self.finite_hls_layout.as_ref().ok().and_then(|layout| layout.packets.get(current_pos)).copied();
let packet_has_payload = packet_layout
.map_or_else(|| matches!((packet[3] >> 4) & 0b11, 0b01 | 0b11), |layout| layout.has_payload);
.map_or_else(|| crate::packet::TsPacket(packet).has_payload(), |layout| layout.has_payload);
// O(1) PID lookup — PID is at most 13 bits (0–8191).
let pid = (u16::from(packet[1] & 0x1F) << 8) | u16::from(packet[2]);
let pid = crate::packet::ts_packet_pid(packet);
let entry = &mut self.cc_entries[pid as usize];
// Normalize payload continuity per PID to a clean local sequence.
let (counter, discontinuity_sent) = entry.get_or_insert((0, false));
@@ -36,9 +36,7 @@ pub(super) fn add_pcr_offset_27mhz(timestamp_27mhz: u64, offset_27mhz: u64) -> u
((u128::from(timestamp_27mhz) + u128::from(offset_27mhz)) % u128::from(MAX_PCR)) as u64
}
pub(super) const TS_PACKET_SIZE: usize = 188;
pub(super) const SYNC_BYTE: u8 = 0x47;
use crate::packet::{TS_PACKET_SIZE, TS_SYNC_BYTE};
pub(super) const PACKET_COUNT: usize = 7;
@@ -58,11 +56,6 @@ pub(super) fn ts_chunk_packet_count() -> usize {
*PACKET_COUNT_OVERRIDE
}
pub(super) const ADAPTATION_FIELD_FLAG_PCR: u8 = 0x10;
// PCR flag bit in adaptation field flags
pub(super) const NULL_PID: u16 = 0x1FFF;
pub(super) const HLS_TS_MAX_PENDING_PES_HEADERS: usize = 64;
pub(super) const HLS_TS_TIMESTAMP_HEADER_BYTES: usize = 19;
@@ -97,7 +90,7 @@ pub(super) fn encode_timestamp(ts: u64) -> [u8; 5] {
/// Decode PCR from 6 bytes (adaptation field) into 42-bit PCR base + 9-bit extension as u64
#[inline]
pub(super) fn decode_pcr(pcr_bytes: &[u8]) -> u64 {
pub(crate) fn decode_pcr(pcr_bytes: &[u8]) -> u64 {
let pcr_base = (u64::from(pcr_bytes[0]) << 25)
| ((u64::from(pcr_bytes[1])) << 17)
| ((u64::from(pcr_bytes[2])) << 9)
@@ -113,21 +106,18 @@ pub(super) enum HlsTsTimestampKind {
Pcr,
}
#[inline]
pub(super) fn ts_packet_pid(packet: &[u8]) -> u16 { (u16::from(packet[1] & 0x1F) << 8) | u16::from(packet[2]) }
pub(super) fn same_finite_ts_packet_layout(source: &[u8], prepared: &[u8]) -> bool {
if source.len() != TS_PACKET_SIZE
|| prepared.len() != TS_PACKET_SIZE
|| source[0] != SYNC_BYTE
|| prepared[0] != SYNC_BYTE
|| source[0] != TS_SYNC_BYTE
|| prepared[0] != TS_SYNC_BYTE
|| source[1] & 0x7F != prepared[1] & 0x7F
|| source[2] != prepared[2]
|| source[3] & 0xF0 != prepared[3] & 0xF0
{
return false;
}
let adaptation_field_control = (source[3] >> 4) & 0b11;
let adaptation_field_control = crate::packet::TsPacket(source).adaptation_field_control();
!matches!(adaptation_field_control, 0b10 | 0b11) || source[4] == prepared[4]
}
@@ -137,7 +127,7 @@ pub(super) fn append_finite_discontinuity_packet(following_packet: &[u8], output
let start = output.len();
output.resize(start.saturating_add(TS_PACKET_SIZE), 0xFF);
let marker = &mut output[start..start + TS_PACKET_SIZE];
marker[0] = SYNC_BYTE;
marker[0] = TS_SYNC_BYTE;
marker[1] = following_packet[1] & 0x1F;
marker[2] = following_packet[2];
// FFmpeg retains this adaptation-only marker as the per-PID CC baseline.
@@ -1,28 +1,17 @@
use super::{
clock::{decode_pcr_at_location, decode_timestamp_at_location, MAX_PTS_DTS, SYNC_BYTE, TS_PACKET_SIZE},
clock::{decode_pcr_at_location, decode_timestamp_at_location, MAX_PTS_DTS},
pes::{inspect_hls_ts_packet, HlsFiniteTsLayoutError},
HlsFiniteTsLayout, HlsFiniteTsPacketLayout, HlsPesHeaderAssembler, HlsTsTimestampFieldKind,
};
use crate::packet::TS_PACKET_SIZE;
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
/// Finds TS alignment by checking for 0x47 sync byte every 188 bytes
/// Finds TS alignment by checking for 0x47 sync byte every 188 bytes, five packets in a row.
pub(super) fn find_ts_alignment(buf: &[u8]) -> Option<usize> {
for offset in 0..TS_PACKET_SIZE {
let mut valid = true;
for i in 0..5 {
if buf.get(offset + i * TS_PACKET_SIZE) != Some(&SYNC_BYTE) {
valid = false;
break;
}
}
if valid {
return Some(offset);
}
}
None
crate::packet::find_sync(buf, 0..TS_PACKET_SIZE, 5, false)
}
pub(super) fn build_hls_finite_ts_layout(buffer: &[u8]) -> Result<HlsFiniteTsLayout, HlsFiniteTsLayoutError> {
@@ -1,11 +1,9 @@
use super::{
clock::{
ts_packet_pid, ADAPTATION_FIELD_FLAG_PCR, HLS_TS_MAX_PENDING_PES_HEADERS, HLS_TS_TIMESTAMP_HEADER_BYTES,
PES_DTS_OFFSET, PES_PTS_OFFSET, SYNC_BYTE, TS_PACKET_SIZE,
},
clock::{HLS_TS_MAX_PENDING_PES_HEADERS, HLS_TS_TIMESTAMP_HEADER_BYTES, PES_DTS_OFFSET, PES_PTS_OFFSET},
HlsCompletedPesTimestamps, HlsCompletedTimestampField, HlsPendingPesHeader, HlsPesHeaderAssembler,
HlsTsPacketEvidence,
};
use crate::packet::{ts_packet_pid, ADAPTATION_FIELD_FLAG_PCR, TS_PACKET_SIZE, TS_SYNC_BYTE};
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -77,11 +75,11 @@ pub(super) fn inspect_hls_ts_packet(
packet: &[u8],
packet_start: usize,
) -> Result<HlsTsPacketEvidence, HlsFiniteTsLayoutError> {
if packet.len() != TS_PACKET_SIZE || packet[0] != SYNC_BYTE || packet[1] & 0x80 != 0 || packet[3] & 0xC0 != 0 {
if packet.len() != TS_PACKET_SIZE || packet[0] != TS_SYNC_BYTE || packet[1] & 0x80 != 0 || packet[3] & 0xC0 != 0 {
return Err(HlsFiniteTsLayoutError::InvalidTransportPacket);
}
let pid = ts_packet_pid(packet);
let adaptation_field_control = (packet[3] >> 4) & 0b11;
let adaptation_field_control = crate::packet::TsPacket(packet).adaptation_field_control();
let continuity_counter = packet[3] & 0x0F;
let (payload_offset, adaptation_length) = match adaptation_field_control {
0b01 => (Some(4), None),
@@ -2,11 +2,12 @@ use super::{
clock::{
add_pts_dts_offset, decode_pcr, decode_pcr_at_location, decode_timestamp, decode_timestamp_at_location,
forward_clock_distance_90khz, HlsTsTimestampKind, HLS_TS_PROFILE_MIN_TOLERANCE_TICKS_90KHZ,
HLS_TS_SPLICE_MIN_GAP_TICKS_90KHZ, MAX_PTS_DTS, TS_PACKET_SIZE,
HLS_TS_SPLICE_MIN_GAP_TICKS_90KHZ, MAX_PTS_DTS,
},
pes::inspect_hls_ts_packet,
HlsFiniteTsLayout, HlsPesHeaderAssembler, HlsTsTimestampProfileAccumulator, HlsTsTimestampProfileScanner,
};
use crate::packet::TS_PACKET_SIZE;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct HlsTsTimestampProfile {
@@ -2,11 +2,12 @@ use super::{
clock::{
add_pcr_offset_27mhz, add_pts_dts_offset, append_finite_discontinuity_packet, decode_pcr_at_location,
decode_timestamp, encode_pcr, encode_timestamp, gather_timestamp_bytes, pcr_offset_27mhz,
same_finite_ts_packet_layout, ts_packet_pid, ADAPTATION_FIELD_FLAG_PCR, NULL_PID, SYNC_BYTE, TS_PACKET_SIZE,
same_finite_ts_packet_layout,
},
pes::HlsFiniteTsLayoutError,
HlsFiniteTsLayout, HlsFiniteTsPacketLayout, TransportStreamBuffer,
};
use crate::packet::{ts_packet_pid, NULL_PID, TS_PACKET_SIZE, TS_SYNC_BYTE};
use bytes::{Bytes, BytesMut};
use std::task::Waker;
@@ -179,20 +180,12 @@ impl TransportStreamBuffer {
out.resize(start + TS_PACKET_SIZE, 0xFF);
let pkt = &mut out[start..start + TS_PACKET_SIZE];
pkt[0] = SYNC_BYTE;
pkt[0] = TS_SYNC_BYTE;
pkt[1] = new_packet[1] & 0x1F;
pkt[2] = new_packet[2];
// Check if the current packet has a PCR (need at least 7 adaptation bytes: 1 flag + 6 PCR).
let new_pkt_has_pcr = {
let afc = (new_packet[3] >> 4) & 0b11;
if afc == 2 || afc == 3 {
let adaptation_len = new_packet[4] as usize;
adaptation_len >= 7 && (new_packet[5] & ADAPTATION_FIELD_FLAG_PCR) != 0
} else {
false
}
};
let new_pkt_has_pcr = crate::packet::TsPacket(new_packet).has_pcr();
// AFC=2 (Adaptation Only), Scrambling=00 (Unscrambled), CC=cc
pkt[3] = 0x20 | (cc & 0x0F);
@@ -2,18 +2,18 @@ use super::{
clock::{
append_finite_discontinuity_packet, decode_pcr, decode_pcr_at_location, decode_timestamp,
decode_timestamp_at_location, encode_pcr, encode_timestamp, forward_clock_distance_90khz,
gather_timestamp_bytes, pcr_offset_27mhz, ts_packet_pid, ADAPTATION_FIELD_FLAG_PCR,
HLS_TS_SPLICE_MIN_GAP_TICKS_90KHZ, MAX_PTS_DTS, NULL_PID, SYNC_BYTE, TS_PACKET_SIZE,
gather_timestamp_bytes, pcr_offset_27mhz, HLS_TS_SPLICE_MIN_GAP_TICKS_90KHZ, MAX_PTS_DTS,
},
layout::build_hls_finite_ts_layout,
pes::inspect_hls_ts_packet,
*,
};
use crate::packet::{ts_packet_pid, ADAPTATION_FIELD_FLAG_PCR, NULL_PID, TS_PACKET_SIZE, TS_SYNC_BYTE};
use bytes::BytesMut;
fn build_pts_dts_payload_packet(pid: u16, cc: u8, pts: u64, dts: u64) -> [u8; TS_PACKET_SIZE] {
let mut packet = [0xFF_u8; TS_PACKET_SIZE];
packet[0] = SYNC_BYTE;
packet[0] = TS_SYNC_BYTE;
packet[1] = 0x40 | ((pid >> 8) as u8 & 0x1F); // PUSI + PID high bits
packet[2] = (pid & 0xFF) as u8;
packet[3] = 0x10 | (cc & 0x0F); // payload only
@@ -42,7 +42,7 @@ fn build_pts_dts_payload_packet(pid: u16, cc: u8, pts: u64, dts: u64) -> [u8; TS
fn build_pts_only_payload_packet(pid: u16, cc: u8, pts: u64) -> [u8; TS_PACKET_SIZE] {
let mut packet = [0xFF_u8; TS_PACKET_SIZE];
packet[0] = SYNC_BYTE;
packet[0] = TS_SYNC_BYTE;
packet[1] = 0x40 | ((pid >> 8) as u8 & 0x1F);
packet[2] = (pid & 0xFF) as u8;
packet[3] = 0x10 | (cc & 0x0F);
@@ -86,7 +86,7 @@ fn build_split_pes_header_packets(
assert!((1..=184).contains(&payload_length));
let mut packet = [0xFF_u8; TS_PACKET_SIZE];
let packet_index = u8::try_from(packet_index).expect("test packet index fits");
packet[0] = SYNC_BYTE;
packet[0] = TS_SYNC_BYTE;
packet[1] = ((pid >> 8) as u8 & 0x1F) | (u8::from(packet_index == 0) * 0x40);
packet[2] = (pid & 0xFF) as u8;
packet[3] = if payload_length == 184 {
@@ -114,7 +114,7 @@ fn build_split_pes_header_packets(
fn build_pts_dts_pcr_packet(pid: u16, cc: u8, pts: u64, dts: u64, pcr_90khz: u64) -> [u8; TS_PACKET_SIZE] {
let mut packet = [0xFF_u8; TS_PACKET_SIZE];
packet[0] = SYNC_BYTE;
packet[0] = TS_SYNC_BYTE;
packet[1] = 0x40 | ((pid >> 8) as u8 & 0x1F);
packet[2] = (pid & 0xFF) as u8;
packet[3] = 0x30 | (cc & 0x0F);
@@ -170,7 +170,7 @@ fn packet_timestamps(packet: &[u8]) -> (u64, u64, u64) {
fn build_adaptation_only_packet(pid: u16, cc: u8) -> [u8; TS_PACKET_SIZE] {
let mut packet = [0xFF_u8; TS_PACKET_SIZE];
packet[0] = SYNC_BYTE;
packet[0] = TS_SYNC_BYTE;
packet[1] = (pid >> 8) as u8 & 0x1F;
packet[2] = (pid & 0xFF) as u8;
packet[3] = 0x20 | (cc & 0x0F); // adaptation only
@@ -188,7 +188,7 @@ fn build_pcr_only_packet(pid: u16, cc: u8, pcr_90khz: u64) -> [u8; TS_PACKET_SIZ
fn build_payload_packet(pid: u16, cc: u8) -> [u8; TS_PACKET_SIZE] {
let mut packet = [0xFF_u8; TS_PACKET_SIZE];
packet[0] = SYNC_BYTE;
packet[0] = TS_SYNC_BYTE;
packet[1] = (pid >> 8) as u8 & 0x1F;
packet[2] = (pid & 0xFF) as u8;
packet[3] = 0x10 | (cc & 0x0F);