Skip to content

H264 depacketization fails with ErrShortPacket #164

Open
@KallDrexx

Description

@KallDrexx

I'm getting Rtp(ErrShortPacket) after several successful depacketize calls from the H264Packete depacketizer, and am struggling to understand what I'm doing wrong.

The minimal reproduction of this is:

use std::sync::Arc;
use std::time::Duration;
use tokio::io::{AsyncBufReadExt, BufReader};
use tracing::{debug, error, info};
use tracing_subscriber::fmt;
use tracing_subscriber::layer::SubscriberExt;
use webrtc::api::APIBuilder;
use webrtc::api::interceptor_registry::register_default_interceptors;
use webrtc::api::media_engine::{MediaEngine, MIME_TYPE_H264};
use webrtc::ice_transport::ice_server::RTCIceServer;
use webrtc::interceptor::registry::Registry;
use webrtc::peer_connection::configuration::RTCConfiguration;
use webrtc::peer_connection::RTCPeerConnection;
use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
use webrtc::rtp;
use webrtc::rtp_transceiver::rtp_codec::{RTCRtpCodecCapability, RTCRtpCodecParameters, RTPCodecType};
use webrtc::rtp_transceiver::rtp_receiver::RTCRtpReceiver;
use webrtc::track::track_remote::TrackRemote;
use crate::rtp::codecs::h264::H264Packet;
use crate::rtp::packetizer::Depacketizer;

#[tokio::main()]
async fn main() {
    let subscriber = tracing_subscriber::registry()
        .with(fmt::Layer::new().with_writer(std::io::stdout).pretty());

    tracing::subscriber::set_global_default(subscriber).expect("Unable to set a global collector");

    println!("Enter base64 sdp: ");
    let stdin = tokio::io::stdin();
    let reader = BufReader::new(stdin);
    let line = reader.lines().next_line().await.unwrap().unwrap();
    let bytes = base64::decode(line).unwrap();
    let json = String::from_utf8(bytes).unwrap();
    let offer = serde_json::from_str::<RTCSessionDescription>(&json).unwrap();

    let _connection = create_connection(offer).await;
    loop {
        tokio::time::sleep(Duration::from_secs(1)).await;
    }
}

async fn receive_rtp_track_media(track: Arc<TrackRemote>) {
    info!("Starting rtp track reader");

    let track_codec = track.codec().await;
    let mime_type = track_codec.capability.mime_type.to_lowercase();
    info!("New RTP track started with mime type '{}'", mime_type);

    if mime_type != MIME_TYPE_H264.to_lowercase() {
        error!("Invalid mime type");
        return;
    }

    let mut has_seen_key_frame = false;
    let mut cached_h264_packet = H264Packet::default();
    while let Ok((rtp_packet, _)) = track.read_rtp().await {
        match handle_h264_packet(rtp_packet, &mut has_seen_key_frame, &mut cached_h264_packet) {
            Ok(()) => (),
            Err(error) => {
                error!("Failed to process rtp packet: {:?}", error);
                break;
            }
        }
    }

    info!("Stopping rtp track reader");
}

fn handle_h264_packet(
    rtp_packet: rtp::packet::Packet,
    has_seen_key_frame: &mut bool,
    cached_h264_packet: &mut H264Packet,
) -> Result<(), webrtc::Error> {
    if rtp_packet.payload.is_empty() {
        return Ok(());
    }

    let is_key_frame = is_key_frame(&rtp_packet.payload);
    if !*has_seen_key_frame && !is_key_frame {
        return Ok(());
    }

    *has_seen_key_frame = true;

    let payload = cached_h264_packet.depacketize(&rtp_packet.payload)?;
    if !payload.is_empty() {
        debug!("H264 packet depacketized");
    }

    Ok(())
}

async fn create_connection(offer: RTCSessionDescription) -> RTCPeerConnection {
    let mut media_engine = MediaEngine::default();
    media_engine.register_codec(
        RTCRtpCodecParameters {
            capability: RTCRtpCodecCapability {
                mime_type: MIME_TYPE_H264.to_owned(),
                clock_rate: 90000,
                channels: 0,
                sdp_fmtp_line: "".to_owned(),
                rtcp_feedback: vec![],
            },
            payload_type: 102,
            ..Default::default()
        },
        RTPCodecType::Video,
    ).expect("Failed to add h264 to media engine");

    let registry = Registry::new();
    let registry = register_default_interceptors(registry, &mut media_engine).unwrap();
    let api = APIBuilder::new()
        .with_media_engine(media_engine)
        .with_interceptor_registry(registry)
        .build();

    let config = RTCConfiguration {
        ice_servers: vec![RTCIceServer {
            urls: vec!["stun:stun.l.google.com:19302".to_owned()],
            ..Default::default()
        }],
        ..Default::default()
    };

    let peer_connection = api.new_peer_connection(config).await.unwrap();
    peer_connection.add_transceiver_from_kind(RTPCodecType::Video, &[]).await.unwrap();

    peer_connection.on_track(
        Box::new(move |track: Option<Arc<TrackRemote>>, _receiver: Option<Arc<RTCRtpReceiver>>| {
            if let Some(track) = track {
                tokio::spawn(receive_rtp_track_media(track));
            }

            Box::pin(async {})
        })
    ).await;


    peer_connection.set_remote_description(offer).await.unwrap();
    let answer = peer_connection.create_answer(None).await.unwrap();
    let mut channel = peer_connection.gathering_complete_promise().await;
    peer_connection.set_local_description(answer).await.unwrap();
    let _ = channel.recv().await;

    let answer = peer_connection.local_description().await.unwrap();
    let json = serde_json::to_string(&answer).unwrap();
    let encoded_json = base64::encode(json);

    debug!("Answer sdp of: {}", encoded_json);

    peer_connection
}

const NALU_TTYPE_STAP_A: u32 = 24;
const NALU_TTYPE_SPS: u32 = 7;
const NALU_TYPE_BITMASK: u32 = 0x1F;

fn is_key_frame(data: &[u8]) -> bool {
    if data.len() < 4 {
        false
    } else {
        let word = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
        let nalu_type = (word >> 24) & NALU_TYPE_BITMASK;
        (nalu_type == NALU_TTYPE_STAP_A && (word & NALU_TYPE_BITMASK) == NALU_TTYPE_SPS)
            || (nalu_type == NALU_TTYPE_SPS)
    }
}

The Cargo.Toml this works with is

[package]
name = "webrtc-test"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bytes = "1"
tokio = { version = "1.9", features = ["full"] }
futures = "0.3"
tracing = "0.1"
tracing-subscriber = { version = "0.3.2", features = ["json"] }
tracing-futures = "0.2.5"
tracing-appender = "0.2.0"
serde_json = "1.0.79"
webrtc-sdp = "0.3.9"
webrtc = "0.4.0"
base64 = "0.13.0"

To test this I

  • Execute this code
  • Point my browser to the jsfiddle from the broadcast example
  • Click Publish a broadcast
  • Copy the base64 SDP into the example program's console window
  • Copy the base64 SDP response from the example program into the browser and click Start session

The jsfiddle log area shows that it's connected, and I have the following logs from the example application

  2022-03-05T18:22:07.925327Z  INFO webrtc_test: Starting rtp track reader
    at src\main.rs:44

  2022-03-05T18:22:07.925377Z  INFO webrtc_test: New RTP track started with mime type 'video/h264'
    at src\main.rs:48

  2022-03-05T18:22:07.925416Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.018040Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.033860Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.034388Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.034461Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.049109Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.096063Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.127287Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.159258Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.191205Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.222239Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.253229Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.285444Z DEBUG webrtc_test: H264 packet depacketized
    at src\main.rs:88

  2022-03-05T18:22:08.315680Z ERROR webrtc_test: Failed to process rtp packet: Rtp(ErrShortPacket)
    at src\main.rs:61

  2022-03-05T18:22:08.315715Z  INFO webrtc_test: Stopping rtp track reader
    at src\main.rs:67

This is showing that it's connected, we depacketize 13 h264 packets in this example before receiving a 2 byte packet with the bytes [9, 48] every time.

This is reproducible 100% of the time for me. What am I doing wrong? Following the save h264 example it does not seem like you are special casing this error. The logic in my handle_h264_packet seems to also match up with the H264Writer logic you are using, unless I am missing something.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions