From 679ab5c7fb7bc3eaea3cf40f80f39ece4158f20e Mon Sep 17 00:00:00 2001 From: winlin Date: Thu, 5 Jun 2025 09:50:35 -0400 Subject: [PATCH 01/24] CICD: Add workflow for WHIP. --- .github/workflows/Dockerfile | 24 ++ .github/workflows/fate-cache.yml | 27 ++ .github/workflows/format-patch.sh | 120 +++++++++ .github/workflows/test.yml | 414 ++++++++++++++++++++++++++++++ .gitignore | 1 + 5 files changed, 586 insertions(+) create mode 100644 .github/workflows/Dockerfile create mode 100644 .github/workflows/fate-cache.yml create mode 100755 .github/workflows/format-patch.sh create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/Dockerfile b/.github/workflows/Dockerfile new file mode 100644 index 0000000000000..78e8d928afe2f --- /dev/null +++ b/.github/workflows/Dockerfile @@ -0,0 +1,24 @@ +# docker build -t ossrs/srs:ffmpeg-fate +# docker push ossrs/srs:ffmpeg-fate +FROM ubuntu:22.04 + +RUN apt-get update && \ + apt-get install -y build-essential git rsync make nasm pkg-config libssl-dev &&\ + rm -rf /var/lib/apt/lists/* + +WORKDIR /opt +RUN git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg + +WORKDIR /opt/ffmpeg +RUN ./configure --enable-openssl --enable-version3 +RUN make -j$(nproc) + +RUN make fate-rsync SAMPLES=/opt/ffmpeg/fate-suite +RUN du -sh /opt/ffmpeg/fate-suite + +# Note that you should use the fate-suite.tar, then extract it out of +# docker, to avoid resync all files. +RUN tar cf fate-suite.tar fate-suite +RUN du -sh /opt/ffmpeg/fate-suite.tar + +ENV FATE_SAMPLES=/opt/ffmpeg/fate-suite diff --git a/.github/workflows/fate-cache.yml b/.github/workflows/fate-cache.yml new file mode 100644 index 0000000000000..f8abc46ccf777 --- /dev/null +++ b/.github/workflows/fate-cache.yml @@ -0,0 +1,27 @@ +name: "FFmpeg FATE Cache" + +on: + workflow_dispatch: + +permissions: read-all + +jobs: + build: + name: "Build FFmpeg Fate Cache" + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Login to docker hub + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0 + with: + username: "${{ secrets.DOCKER_USERNAME }}" + password: "${{ secrets.DOCKER_PASSWORD }}" + - name: Build FFmpeg Fate Cache + run: | + set -euxo pipefail + docker build -t ossrs/srs:ffmpeg-fate -f .github/workflows/Dockerfile . + - name: Push FFmpeg Fate Cache + run: | + set -euxo pipefail + docker push ossrs/srs:ffmpeg-fate + runs-on: ubuntu-22.04 diff --git a/.github/workflows/format-patch.sh b/.github/workflows/format-patch.sh new file mode 100755 index 0000000000000..cd0ed83ef2c84 --- /dev/null +++ b/.github/workflows/format-patch.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +if [[ $(jq --version 1>/dev/null 2>&1 && echo yes) != "yes" ]]; then + echo "Tool jq is not installed. Please install it to parse JSON data. For example:" + echo " apt install jq" + echo " brew install jq" + echo " yum install jq" + echo "See https://github.com/jqlang/jq" + exit 1 +fi + +if [ -z "$1" ]; then + echo "Please provide a PR link or number. For example: https://github.com/ossrs/ffmpeg-webrtc/pull/20" + exit 1 +fi + +if [[ "$1" =~ ^https://github.com/ossrs/ffmpeg-webrtc/pull/([0-9]+)$ ]]; then + PR_NUMBER="${BASH_REMATCH[1]}" +elif [[ "$1" =~ ^[0-9]+$ ]]; then + PR_NUMBER="$1" +else + echo "Invalid input format. Please provide a PR link or number. For example: https://github.com/ossrs/ffmpeg-webrtc/pull/20" + exit 1 +fi + +PR_URL="https://github.com/ossrs/ffmpeg-webrtc/pull/$PR_NUMBER" +echo "Fetching PR #$PR_NUMBER from $PR_URL" + +PR_DATA=$(curl -s "https://api.github.com/repos/ossrs/ffmpeg-webrtc/pulls/$PR_NUMBER") +REPO_NAME=$(echo "$PR_DATA" | jq -r '.head.repo.full_name') +BRANCH_NAME=$(echo "$PR_DATA" | jq -r '.head.ref') +if [[ -z "$REPO_NAME" || -z "$BRANCH_NAME" ]]; then + echo "Error: REPO_NAME or BRANCH_NAME is empty!" + exit 1 +fi +echo "Repository: $REPO_NAME, Branch: $BRANCH_NAME" + +PR_TITLE=$(echo "$PR_DATA" | jq -r '.title') +PR_DESCRIPTION=$(echo "$PR_DATA" | jq -r '.body // ""') +if [[ -z "$PR_TITLE" ]]; then + echo "Error: PR title is empty!" + exit 1 +fi + +echo "PR information:" +echo "===================================================================" +echo "$PR_TITLE" +echo "$PR_DESCRIPTION" +echo "===================================================================" +echo "" + +set -euxo pipefail + +git checkout workflows +echo "Switched to workflows branch." + +git pull +echo "Pulled latest changes from workflows branch." + +REMOTE_NAME=patch-tmp +if git remote | grep -q "^$REMOTE_NAME$"; then + git remote rm "$REMOTE_NAME" +fi +git remote add $REMOTE_NAME https://github.com/${REPO_NAME}.git +git fetch $REMOTE_NAME $BRANCH_NAME +echo "Fetch remote $REMOTE_NAME at $(git remote get-url $REMOTE_NAME)" + +TMP_BRANCH=tmp-branch-for-patch-$PR_NUMBER +if git branch --list "$TMP_BRANCH" | grep -q "^..$TMP_BRANCH$"; then + git branch -D "$TMP_BRANCH" +fi +git checkout -b $TMP_BRANCH $REMOTE_NAME/$BRANCH_NAME +echo "Checkout branch $TMP_BRANCH from $REMOTE_NAME/$BRANCH_NAME" + +FIRST_AUTHOR_NAME=$(git log workflows..HEAD --reverse --format='%an' | head -n1) +FIRST_AUTHOR_EMAIL=$(git log workflows..HEAD --reverse --format='%ae' | head -n1) +echo "Author: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" + +COAUTHORS=$(git log workflows..HEAD --format='Co-authored-by: %an <%ae>' |grep -v "$FIRST_AUTHOR_NAME" | sort -u) +COAUTHOR_COUNT=$(echo "$COAUTHORS" | wc -l) +if [ "$COAUTHOR_COUNT" -gt 0 ]; then + echo "$COAUTHORS" +fi + +COMMIT_MSG="$PR_TITLE" +if [ -n "$PR_DESCRIPTION" ]; then + COMMIT_MSG="$COMMIT_MSG\n\n$PR_DESCRIPTION" +fi + +if [ "$COAUTHOR_COUNT" -gt 0 ]; then + COMMIT_MSG="$COMMIT_MSG\n" + COMMIT_MSG="$COMMIT_MSG\n$COAUTHORS" +fi + +echo "Commit information:" +echo "Author: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" +echo "===================================================================" +echo -e "$COMMIT_MSG" +echo "===================================================================" +echo "" + +git rebase workflows +git reset --soft workflows +git commit --author "$FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" -m "$(echo -e "$COMMIT_MSG")" +echo "Squashed commits into a single commit." +git log -1 --pretty=format:"%an <%ae> %h %s" + +PATCH_FILE="patch-$PR_NUMBER-$(date +%s).patch" +rm -f $PATCH_FILE +git format-patch -1 --stdout > $PATCH_FILE + +git checkout workflows +#git br -D $TMP_BRANCH +#echo "Removed temporary branch $TMP_BRANCH." + +set +e +u +x +o pipefail + +echo "" +echo "Patch file created: $PATCH_FILE" +echo "" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000000..8dae8e9c5c471 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,414 @@ +name: "Test" + +on: + push: + pull_request: + +permissions: read-all + +# Results for commonly used commands: +# $(pwd) is /home/runner/work/ffmpeg-webrtc/ffmpeg-webrtc +# $(nproc) is 4 +# $(whoami) is runner +# $(id -gn) is docker +# $(which docker) is /usr/bin/docker +# $(ifconfig eth0 | grep 'inet ' | awk '{print $2}') is private IP4 address like 10.1.0.76 +jobs: + build: + name: "Build FFmpeg" + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config libssl-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + runs-on: ubuntu-22.04 + + fate: + name: "FFmpeg Fate Test" + steps: + - name: Checkout repository + uses: actions/checkout@v4 + # The cache for FFmpeg FATE samples can help decrease the resync time when executing + # "make fate-resync." The cache is stored in the Docker image "ossrs/srs:ffmpeg-fate," + # which can be refreshed by manually executing the below workflow. + # https://github.com/ossrs/ffmpeg-webrtc/actions/workflows/fate-cache.yml + - name: Download Fate Cache Samples + run: | + set -euxo pipefail + + docker run --rm -v $(pwd):/target ossrs/srs:ffmpeg-fate \ + bash -c "cp /opt/ffmpeg/fate-suite.tar /target/" + tar xf fate-suite.tar + + ls -ldh fate-suite + du -sh fate-suite + - name: Configure FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config libssl-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ + --extra-cflags='-fsanitize=address -g -O0' --extra-cxxflags='-fsanitize=address -g -O0' --extra-ldflags='-fsanitize=address -g -O0' + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + - name: FFmpeg Fate rsync + run: | + set -euxo pipefail + make fate-rsync SAMPLES=$(pwd)/fate-suite + - name: Stat Fate Suite + run: | + set -euxo pipefail + du -sh fate-suite + du -sh * + - name: Run FFmpeg Fate + run: | + set -euxo pipefail + make fate -j$(nproc) SAMPLES=$(pwd)/fate-suite + runs-on: ubuntu-22.04 + + srs: + name: "FFmpeg with SRS" + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config jq libssl-dev libopus-dev libx264-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ + --enable-libx264 --enable-gpl --enable-libopus + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + - name: Start SRS Docker container + run: | + set -euxo pipefail + ip=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}') + docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ + --env CANDIDATE=$ip -p 8000:8000/udp \ + ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf + - name: Test FFmpeg with SRS + run: | + set -euxo pipefail + + # Publish a test stream to SRS using the whip muxer + nohup ./ffmpeg -t 30 -re -f lavfi -i testsrc=size=1280x720 -f lavfi -i sine=frequency=440 -pix_fmt yuv420p \ + -vcodec libx264 -profile:v baseline -r 25 -g 50 -acodec libopus -ar 48000 -ac 2 \ + -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ + >/dev/null 2>&1 & + + # Check streams in SRS. + for ((i=0; i<10; i++)); do + STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') + if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + sleep 3 + done + if [[ "$STREAM" != "livestream" ]]; then + echo "Stream not found: $STREAM" + exit 1 + fi + runs-on: ubuntu-22.04 + + openssl-1-0-1k: + name: "With OpenSSL 1.0.1k" + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build OpenSSL 1.0.1k + run: | + set -euxo pipefail + curl -s -L https://www.openssl.org/source/openssl-1.0.1k.tar.gz | tar xz + cd openssl-1.0.1k && ./config && make -j1 && sudo make install_sw + - name: Download Test File + run: | + set -euxo pipefail + curl -s -L -O https://github.com/ossrs/ffmpeg-webrtc/releases/download/pre-release/bbb-4mbps-baseline-opus.mp4 + - name: Build FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config jq libopus-dev libx264-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + - name: Start SRS Docker container + run: | + set -euxo pipefail + ip=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}') + docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ + --env CANDIDATE=$ip -p 8000:8000/udp \ + ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf + - name: Test FFmpeg with SRS + run: | + set -euxo pipefail + + # Publish a test stream to SRS using the whip muxer + nohup ./ffmpeg -t 30 -re -i bbb-4mbps-baseline-opus.mp4 -c copy \ + -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ + >/dev/null 2>&1 & + + # Check streams in SRS. + for ((i=0; i<10; i++)); do + STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') + if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + sleep 3 + done + if [[ "$STREAM" != "livestream" ]]; then + echo "Stream not found: $STREAM" + exit 1 + fi + runs-on: ubuntu-22.04 + + openssl-1-0-2: + name: "With OpenSSL 1.0.2" + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build OpenSSL 1.0.2 + run: | + set -euxo pipefail + curl -s -L https://www.openssl.org/source/openssl-1.0.2.tar.gz | tar xz + cd openssl-1.0.2 && ./config && make -j1 && sudo make install_sw + - name: Download Test File + run: | + set -euxo pipefail + curl -s -L -O https://github.com/ossrs/ffmpeg-webrtc/releases/download/pre-release/bbb-4mbps-baseline-opus.mp4 + - name: Build FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config jq libopus-dev libx264-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + - name: Start SRS Docker container + run: | + set -euxo pipefail + ip=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}') + docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ + --env CANDIDATE=$ip -p 8000:8000/udp \ + ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf + - name: Test FFmpeg with SRS + run: | + set -euxo pipefail + + # Publish a test stream to SRS using the whip muxer + nohup ./ffmpeg -t 30 -re -i bbb-4mbps-baseline-opus.mp4 -c copy \ + -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ + >/dev/null 2>&1 & + + # Check streams in SRS. + for ((i=0; i<10; i++)); do + STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') + if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + sleep 3 + done + if [[ "$STREAM" != "livestream" ]]; then + echo "Stream not found: $STREAM" + exit 1 + fi + runs-on: ubuntu-22.04 + + openssl-1-1-0h: + name: "With OpenSSL 1.1.0h" + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build OpenSSL 1.1.0h + run: | + set -euxo pipefail + curl -s -L https://www.openssl.org/source/openssl-1.1.0h.tar.gz | tar xz + cd openssl-1.1.0h && ./config && make -j$(nproc) && sudo make install_sw + - name: Download Test File + run: | + set -euxo pipefail + curl -s -L -O https://github.com/ossrs/ffmpeg-webrtc/releases/download/pre-release/bbb-4mbps-baseline-opus.mp4 + - name: Build FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config jq libopus-dev libx264-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + - name: Start SRS Docker container + run: | + set -euxo pipefail + ip=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}') + docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ + --env CANDIDATE=$ip -p 8000:8000/udp \ + ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf + - name: Test FFmpeg with SRS + run: | + set -euxo pipefail + + # Publish a test stream to SRS using the whip muxer + nohup ./ffmpeg -t 30 -re -i bbb-4mbps-baseline-opus.mp4 -c copy \ + -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ + >/dev/null 2>&1 & + + # Check streams in SRS. + for ((i=0; i<10; i++)); do + STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') + if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + sleep 3 + done + if [[ "$STREAM" != "livestream" ]]; then + echo "Stream not found: $STREAM" + exit 1 + fi + runs-on: ubuntu-22.04 + + openssl-3-0: + name: "With OpenSSL 3.0" + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build OpenSSL 3.0 + run: | + set -euxo pipefail + curl -s -L https://www.openssl.org/source/openssl-3.0.0.tar.gz | tar xz + cd openssl-3.0.0 && ./config && make -j$(nproc) && sudo make install_sw + - name: Build FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config jq libopus-dev libx264-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ + --enable-libx264 --enable-gpl --enable-libopus + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + - name: Start SRS Docker container + run: | + set -euxo pipefail + ip=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}') + docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ + --env CANDIDATE=$ip -p 8000:8000/udp \ + ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf + - name: Test FFmpeg with SRS + run: | + set -euxo pipefail + + # Publish a test stream to SRS using the whip muxer + nohup ./ffmpeg -t 30 -re -f lavfi -i testsrc=size=1280x720 -f lavfi -i sine=frequency=440 -pix_fmt yuv420p \ + -vcodec libx264 -profile:v baseline -r 25 -g 50 -acodec libopus -ar 48000 -ac 2 \ + -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ + >/dev/null 2>&1 & + + # Check streams in SRS. + for ((i=0; i<10; i++)); do + STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') + if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + sleep 3 + done + if [[ "$STREAM" != "livestream" ]]; then + echo "Stream not found: $STREAM" + exit 1 + fi + runs-on: ubuntu-22.04 + + openssl-latest: + name: "With OpenSSL latest" + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build OpenSSL latest + run: | + set -euxo pipefail + curl -s -L https://www.openssl.org/source/openssl-3.5.0.tar.gz | tar xz + cd openssl-3.5.0 && ./config && make -j$(nproc) && sudo make install_sw + - name: Build FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config jq libopus-dev libx264-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ + --enable-libx264 --enable-gpl --enable-libopus + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + - name: Start SRS Docker container + run: | + set -euxo pipefail + ip=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}') + docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ + --env CANDIDATE=$ip -p 8000:8000/udp \ + ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf + - name: Test FFmpeg with SRS + run: | + set -euxo pipefail + + # Publish a test stream to SRS using the whip muxer + nohup ./ffmpeg -t 30 -re -f lavfi -i testsrc=size=1280x720 -f lavfi -i sine=frequency=440 -pix_fmt yuv420p \ + -vcodec libx264 -profile:v baseline -r 25 -g 50 -acodec libopus -ar 48000 -ac 2 \ + -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ + >/dev/null 2>&1 & + + # Check streams in SRS. + for ((i=0; i<10; i++)); do + STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') + if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + sleep 3 + done + if [[ "$STREAM" != "livestream" ]]; then + echo "Stream not found: $STREAM" + exit 1 + fi + runs-on: ubuntu-22.04 + + test-done: + needs: + - fate + - srs + - openssl-1-0-1k + - openssl-1-0-2 + - openssl-1-1-0h + - openssl-3-0 + - openssl-latest + steps: + - run: echo 'All done' + runs-on: ubuntu-22.04 + diff --git a/.gitignore b/.gitignore index 59c89da5e03b5..da2f89a60dcba 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ *.ptx *.ptx.c *.ptx.gz +*.patch *_g \#* .\#* From 4a6572803889d65b8a2aefab85b8d3807f25c531 Mon Sep 17 00:00:00 2001 From: Winlin Date: Sat, 7 Jun 2025 18:04:37 -0400 Subject: [PATCH 02/24] CICD: Use specified openssl. Add crash case. (#21) --- .github/workflows/format-patch.sh | 8 +- .github/workflows/test.yml | 325 +++++++++++++++++++++++++----- .gitignore | 1 + 3 files changed, 285 insertions(+), 49 deletions(-) diff --git a/.github/workflows/format-patch.sh b/.github/workflows/format-patch.sh index cd0ed83ef2c84..b7fe8c560f8aa 100755 --- a/.github/workflows/format-patch.sh +++ b/.github/workflows/format-patch.sh @@ -27,16 +27,16 @@ PR_URL="https://github.com/ossrs/ffmpeg-webrtc/pull/$PR_NUMBER" echo "Fetching PR #$PR_NUMBER from $PR_URL" PR_DATA=$(curl -s "https://api.github.com/repos/ossrs/ffmpeg-webrtc/pulls/$PR_NUMBER") -REPO_NAME=$(echo "$PR_DATA" | jq -r '.head.repo.full_name') -BRANCH_NAME=$(echo "$PR_DATA" | jq -r '.head.ref') +REPO_NAME=$(printf '%s' "$PR_DATA" | jq -r '.head.repo.full_name') +BRANCH_NAME=$(printf '%s' "$PR_DATA" | jq -r '.head.ref') if [[ -z "$REPO_NAME" || -z "$BRANCH_NAME" ]]; then echo "Error: REPO_NAME or BRANCH_NAME is empty!" exit 1 fi echo "Repository: $REPO_NAME, Branch: $BRANCH_NAME" -PR_TITLE=$(echo "$PR_DATA" | jq -r '.title') -PR_DESCRIPTION=$(echo "$PR_DATA" | jq -r '.body // ""') +PR_TITLE=$(printf '%s' "$PR_DATA" | jq -r '.title') +PR_DESCRIPTION=$(printf '%s' "$PR_DATA" | jq -r '.body // ""') if [[ -z "$PR_TITLE" ]]; then echo "Error: PR title is empty!" exit 1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8dae8e9c5c471..cb604c11e9dee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,7 @@ on: permissions: read-all # Results for commonly used commands: +# $HOME is /home/runner # $(pwd) is /home/runner/work/ffmpeg-webrtc/ffmpeg-webrtc # $(nproc) is 4 # $(whoami) is runner @@ -106,26 +107,129 @@ jobs: docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ --env CANDIDATE=$ip -p 8000:8000/udp \ ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf - - name: Test FFmpeg with SRS + - name: Streaming with FFmpeg run: | set -euxo pipefail + nohup ./ffmpeg -t 30 -re -f lavfi -i testsrc=size=1280x720 -f lavfi -i sine=frequency=440 -pix_fmt yuv420p \ + -vcodec libx264 -profile:v baseline -r 25 -g 50 -acodec libopus -ar 48000 -ac 2 \ + -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ + 1>ffstdout.log 2>ffstderr.log & + - name: Check SRS Streaming + id: streaming + run: | + set -euxo pipefail + + # Check streams in SRS. + for ((i=0; i<10; i++)); do + STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') + if [[ "$STREAM" == "livestream" ]]; then + echo 'Test OK'; + echo "has_stream=true" >> $GITHUB_OUTPUT + break; + fi + sleep 3 + done - # Publish a test stream to SRS using the whip muxer + if [[ "$STREAM" != "livestream" ]]; then + echo "Stream not found: $STREAM" + echo "has_stream=false" >> $GITHUB_OUTPUT + fi + - name: Stop FFmpeg normally + run: | + pkill -SIGINT ffmpeg && sleep 3 || + echo "FFmpeg process not found or already stopped." + - name: Show FFmpeg Stdout Log + run: cat ffstdout.log + - name: Show FFmpeg Stderr Log + run: cat ffstderr.log + - name: Check FFmpeg Exit Log + run: | + set -euxo pipefail + cat ffstderr.log |grep 'Exiting normally' && exit 0 + echo "Exiting normally not found in ffstderr.log" && exit 1 + - name: Check Stream Existence + if: ${{ steps.streaming.outputs.has_stream == 'false' }} + run: exit 1 + runs-on: ubuntu-22.04 + + asan: + name: "FFmpeg with Asan" + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config jq libssl-dev libopus-dev libx264-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ + --enable-libx264 --enable-gpl --enable-libopus \ + --extra-cflags='-fsanitize=address -g -O0' --extra-cxxflags='-fsanitize=address -g -O0' --extra-ldflags='-fsanitize=address -g -O0' + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + - name: Start SRS Docker container + run: | + set -euxo pipefail + ip=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}') + docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ + --env CANDIDATE=$ip -p 8000:8000/udp \ + ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf + - name: Streaming with FFmpeg + run: | + set -euxo pipefail nohup ./ffmpeg -t 30 -re -f lavfi -i testsrc=size=1280x720 -f lavfi -i sine=frequency=440 -pix_fmt yuv420p \ -vcodec libx264 -profile:v baseline -r 25 -g 50 -acodec libopus -ar 48000 -ac 2 \ -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ - >/dev/null 2>&1 & + 1>ffstdout.log 2>ffstderr.log & + - name: Check SRS Streaming + id: streaming + run: | + set -euxo pipefail # Check streams in SRS. for ((i=0; i<10; i++)); do STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') - if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + if [[ "$STREAM" == "livestream" ]]; then + echo 'Test OK'; + echo "has_stream=true" >> $GITHUB_OUTPUT + break; + fi sleep 3 done + if [[ "$STREAM" != "livestream" ]]; then echo "Stream not found: $STREAM" - exit 1 + echo "has_stream=false" >> $GITHUB_OUTPUT fi + - name: Stop FFmpeg normally + run: | + # TEST: Generate a coredump. + #pkill -SIGSEGV ffmpeg && sleep 3 && exit 0 + pkill -SIGINT ffmpeg && sleep 3 || + echo "FFmpeg process not found or already stopped." + - name: Show FFmpeg Stdout Log + run: cat ffstdout.log + - name: Show FFmpeg Stderr Log + run: cat ffstderr.log + - name: Check Asan Log + run: | + set -euxo pipefail + cat ffstderr.log |grep 'ERROR: AddressSanitizer' && + echo "AddressSanitizer error found in ffstderr.log" && exit 1 + echo "AddressSanitizer is ok" + - name: Check FFmpeg Exit Log + run: | + set -euxo pipefail + cat ffstderr.log |grep 'Exiting normally' && exit 0 + echo "Exiting normally not found in ffstderr.log" && exit 1 + - name: Check Stream Existence + if: ${{ steps.streaming.outputs.has_stream == 'false' }} + run: exit 1 runs-on: ubuntu-22.04 openssl-1-0-1k: @@ -138,7 +242,9 @@ jobs: run: | set -euxo pipefail curl -s -L https://www.openssl.org/source/openssl-1.0.1k.tar.gz | tar xz - cd openssl-1.0.1k && ./config && make -j1 && sudo make install_sw + cd openssl-1.0.1k + ./config --prefix=$HOME/.release/openssl && make -j1 + sudo make install_sw - name: Download Test File run: | set -euxo pipefail @@ -152,7 +258,8 @@ jobs: sudo apt-get install -y nasm pkg-config jq libopus-dev libx264-dev # Build FFmpeg with WebRTC support - ./configure --enable-muxer=whip --enable-openssl --enable-version3 + PKG_CONFIG_PATH="$HOME/.release/openssl/lib/pkgconfig" \ + ./configure --enable-muxer=whip --enable-openssl --enable-version3 make -j$(nproc) ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip - name: Start SRS Docker container @@ -162,25 +269,48 @@ jobs: docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ --env CANDIDATE=$ip -p 8000:8000/udp \ ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf - - name: Test FFmpeg with SRS + - name: Streaming with FFmpeg run: | set -euxo pipefail - - # Publish a test stream to SRS using the whip muxer nohup ./ffmpeg -t 30 -re -i bbb-4mbps-baseline-opus.mp4 -c copy \ -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ - >/dev/null 2>&1 & + 1>ffstdout.log 2>ffstderr.log & + - name: Check SRS Streaming + id: streaming + run: | + set -euxo pipefail # Check streams in SRS. for ((i=0; i<10; i++)); do STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') - if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + if [[ "$STREAM" == "livestream" ]]; then + echo 'Test OK'; + echo "has_stream=true" >> $GITHUB_OUTPUT + break; + fi sleep 3 done + if [[ "$STREAM" != "livestream" ]]; then echo "Stream not found: $STREAM" - exit 1 + echo "has_stream=false" >> $GITHUB_OUTPUT fi + - name: Stop FFmpeg normally + run: | + pkill -SIGINT ffmpeg && sleep 3 || + echo "FFmpeg process not found or already stopped." + - name: Show FFmpeg Stdout Log + run: cat ffstdout.log + - name: Show FFmpeg Stderr Log + run: cat ffstderr.log + - name: Check FFmpeg Exit Log + run: | + set -euxo pipefail + cat ffstderr.log |grep 'Exiting normally' && exit 0 + echo "Exiting normally not found in ffstderr.log" && exit 1 + - name: Check Stream Existence + if: ${{ steps.streaming.outputs.has_stream == 'false' }} + run: exit 1 runs-on: ubuntu-22.04 openssl-1-0-2: @@ -193,7 +323,9 @@ jobs: run: | set -euxo pipefail curl -s -L https://www.openssl.org/source/openssl-1.0.2.tar.gz | tar xz - cd openssl-1.0.2 && ./config && make -j1 && sudo make install_sw + cd openssl-1.0.2 + ./config --prefix=$HOME/.release/openssl + make -j1 && sudo make install_sw - name: Download Test File run: | set -euxo pipefail @@ -207,7 +339,8 @@ jobs: sudo apt-get install -y nasm pkg-config jq libopus-dev libx264-dev # Build FFmpeg with WebRTC support - ./configure --enable-muxer=whip --enable-openssl --enable-version3 + PKG_CONFIG_PATH="$HOME/.release/openssl/lib/pkgconfig" \ + ./configure --enable-muxer=whip --enable-openssl --enable-version3 make -j$(nproc) ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip - name: Start SRS Docker container @@ -217,25 +350,48 @@ jobs: docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ --env CANDIDATE=$ip -p 8000:8000/udp \ ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf - - name: Test FFmpeg with SRS + - name: Streaming with FFmpeg run: | set -euxo pipefail - - # Publish a test stream to SRS using the whip muxer nohup ./ffmpeg -t 30 -re -i bbb-4mbps-baseline-opus.mp4 -c copy \ -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ - >/dev/null 2>&1 & + 1>ffstdout.log 2>ffstderr.log & + - name: Check SRS Streaming + id: streaming + run: | + set -euxo pipefail # Check streams in SRS. for ((i=0; i<10; i++)); do STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') - if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + if [[ "$STREAM" == "livestream" ]]; then + echo 'Test OK'; + echo "has_stream=true" >> $GITHUB_OUTPUT + break; + fi sleep 3 done + if [[ "$STREAM" != "livestream" ]]; then echo "Stream not found: $STREAM" - exit 1 + echo "has_stream=false" >> $GITHUB_OUTPUT fi + - name: Stop FFmpeg normally + run: | + pkill -SIGINT ffmpeg && sleep 3 || + echo "FFmpeg process not found or already stopped." + - name: Show FFmpeg Stdout Log + run: cat ffstdout.log + - name: Show FFmpeg Stderr Log + run: cat ffstderr.log + - name: Check FFmpeg Exit Log + run: | + set -euxo pipefail + cat ffstderr.log |grep 'Exiting normally' && exit 0 + echo "Exiting normally not found in ffstderr.log" && exit 1 + - name: Check Stream Existence + if: ${{ steps.streaming.outputs.has_stream == 'false' }} + run: exit 1 runs-on: ubuntu-22.04 openssl-1-1-0h: @@ -248,7 +404,9 @@ jobs: run: | set -euxo pipefail curl -s -L https://www.openssl.org/source/openssl-1.1.0h.tar.gz | tar xz - cd openssl-1.1.0h && ./config && make -j$(nproc) && sudo make install_sw + cd openssl-1.1.0h + ./config --prefix=$HOME/.release/openssl + make -j$(nproc) && sudo make install_sw - name: Download Test File run: | set -euxo pipefail @@ -262,7 +420,8 @@ jobs: sudo apt-get install -y nasm pkg-config jq libopus-dev libx264-dev # Build FFmpeg with WebRTC support - ./configure --enable-muxer=whip --enable-openssl --enable-version3 + PKG_CONFIG_PATH="$HOME/.release/openssl/lib/pkgconfig" \ + ./configure --enable-muxer=whip --enable-openssl --enable-version3 make -j$(nproc) ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip - name: Start SRS Docker container @@ -272,25 +431,48 @@ jobs: docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ --env CANDIDATE=$ip -p 8000:8000/udp \ ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf - - name: Test FFmpeg with SRS + - name: Streaming with FFmpeg run: | set -euxo pipefail - - # Publish a test stream to SRS using the whip muxer nohup ./ffmpeg -t 30 -re -i bbb-4mbps-baseline-opus.mp4 -c copy \ -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ - >/dev/null 2>&1 & + 1>ffstdout.log 2>ffstderr.log & + - name: Check SRS Streaming + id: streaming + run: | + set -euxo pipefail # Check streams in SRS. for ((i=0; i<10; i++)); do STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') - if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + if [[ "$STREAM" == "livestream" ]]; then + echo 'Test OK'; + echo "has_stream=true" >> $GITHUB_OUTPUT + break; + fi sleep 3 done + if [[ "$STREAM" != "livestream" ]]; then echo "Stream not found: $STREAM" - exit 1 + echo "has_stream=false" >> $GITHUB_OUTPUT fi + - name: Stop FFmpeg normally + run: | + pkill -SIGINT ffmpeg && sleep 3 || + echo "FFmpeg process not found or already stopped." + - name: Show FFmpeg Stdout Log + run: cat ffstdout.log + - name: Show FFmpeg Stderr Log + run: cat ffstderr.log + - name: Check FFmpeg Exit Log + run: | + set -euxo pipefail + cat ffstderr.log |grep 'Exiting normally' && exit 0 + echo "Exiting normally not found in ffstderr.log" && exit 1 + - name: Check Stream Existence + if: ${{ steps.streaming.outputs.has_stream == 'false' }} + run: exit 1 runs-on: ubuntu-22.04 openssl-3-0: @@ -303,7 +485,9 @@ jobs: run: | set -euxo pipefail curl -s -L https://www.openssl.org/source/openssl-3.0.0.tar.gz | tar xz - cd openssl-3.0.0 && ./config && make -j$(nproc) && sudo make install_sw + cd openssl-3.0.0 + ./config --prefix=$HOME/.release/openssl + make -j$(nproc) && sudo make install_sw - name: Build FFmpeg run: | set -euxo pipefail @@ -313,7 +497,8 @@ jobs: sudo apt-get install -y nasm pkg-config jq libopus-dev libx264-dev # Build FFmpeg with WebRTC support - ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ + PKG_CONFIG_PATH="$HOME/.release/openssl/lib/pkgconfig" \ + ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ --enable-libx264 --enable-gpl --enable-libopus make -j$(nproc) ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip @@ -324,26 +509,49 @@ jobs: docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ --env CANDIDATE=$ip -p 8000:8000/udp \ ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf - - name: Test FFmpeg with SRS + - name: Streaming with FFmpeg run: | set -euxo pipefail - - # Publish a test stream to SRS using the whip muxer nohup ./ffmpeg -t 30 -re -f lavfi -i testsrc=size=1280x720 -f lavfi -i sine=frequency=440 -pix_fmt yuv420p \ -vcodec libx264 -profile:v baseline -r 25 -g 50 -acodec libopus -ar 48000 -ac 2 \ -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ - >/dev/null 2>&1 & + 1>ffstdout.log 2>ffstderr.log & + - name: Check SRS Streaming + id: streaming + run: | + set -euxo pipefail # Check streams in SRS. for ((i=0; i<10; i++)); do STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') - if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + if [[ "$STREAM" == "livestream" ]]; then + echo 'Test OK'; + echo "has_stream=true" >> $GITHUB_OUTPUT + break; + fi sleep 3 done + if [[ "$STREAM" != "livestream" ]]; then echo "Stream not found: $STREAM" - exit 1 + echo "has_stream=false" >> $GITHUB_OUTPUT fi + - name: Stop FFmpeg normally + run: | + pkill -SIGINT ffmpeg && sleep 3 || + echo "FFmpeg process not found or already stopped." + - name: Show FFmpeg Stdout Log + run: cat ffstdout.log + - name: Show FFmpeg Stderr Log + run: cat ffstderr.log + - name: Check FFmpeg Exit Log + run: | + set -euxo pipefail + cat ffstderr.log |grep 'Exiting normally' && exit 0 + echo "Exiting normally not found in ffstderr.log" && exit 1 + - name: Check Stream Existence + if: ${{ steps.streaming.outputs.has_stream == 'false' }} + run: exit 1 runs-on: ubuntu-22.04 openssl-latest: @@ -356,7 +564,9 @@ jobs: run: | set -euxo pipefail curl -s -L https://www.openssl.org/source/openssl-3.5.0.tar.gz | tar xz - cd openssl-3.5.0 && ./config && make -j$(nproc) && sudo make install_sw + cd openssl-3.5.0 + ./config --prefix=$HOME/.release/openssl + make -j$(nproc) && sudo make install_sw - name: Build FFmpeg run: | set -euxo pipefail @@ -366,7 +576,8 @@ jobs: sudo apt-get install -y nasm pkg-config jq libopus-dev libx264-dev # Build FFmpeg with WebRTC support - ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ + PKG_CONFIG_PATH="$HOME/.release/openssl/lib/pkgconfig" \ + ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ --enable-libx264 --enable-gpl --enable-libopus make -j$(nproc) ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip @@ -377,32 +588,56 @@ jobs: docker run --rm -d -p 1935:1935 -p 1985:1985 -p 8080:8080 \ --env CANDIDATE=$ip -p 8000:8000/udp \ ossrs/srs:5 ./objs/srs -c conf/rtc2rtmp.conf - - name: Test FFmpeg with SRS + - name: Streaming with FFmpeg run: | set -euxo pipefail - - # Publish a test stream to SRS using the whip muxer nohup ./ffmpeg -t 30 -re -f lavfi -i testsrc=size=1280x720 -f lavfi -i sine=frequency=440 -pix_fmt yuv420p \ -vcodec libx264 -profile:v baseline -r 25 -g 50 -acodec libopus -ar 48000 -ac 2 \ -f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream" \ - >/dev/null 2>&1 & + 1>ffstdout.log 2>ffstderr.log & + - name: Check SRS Streaming + id: streaming + run: | + set -euxo pipefail # Check streams in SRS. for ((i=0; i<10; i++)); do STREAM=$(curl -s http://localhost:1985/api/v1/streams/ | jq -r '.streams[].name') - if [[ "$STREAM" == "livestream" ]]; then echo 'Test OK'; break; fi + if [[ "$STREAM" == "livestream" ]]; then + echo 'Test OK'; + echo "has_stream=true" >> $GITHUB_OUTPUT + break; + fi sleep 3 done + if [[ "$STREAM" != "livestream" ]]; then echo "Stream not found: $STREAM" - exit 1 + echo "has_stream=false" >> $GITHUB_OUTPUT fi + - name: Stop FFmpeg normally + run: | + pkill -SIGINT ffmpeg && sleep 3 || + echo "FFmpeg process not found or already stopped." + - name: Show FFmpeg Stdout Log + run: cat ffstdout.log + - name: Show FFmpeg Stderr Log + run: cat ffstderr.log + - name: Check FFmpeg Exit Log + run: | + set -euxo pipefail + cat ffstderr.log |grep 'Exiting normally' && exit 0 + echo "Exiting normally not found in ffstderr.log" && exit 1 + - name: Check Stream Existence + if: ${{ steps.streaming.outputs.has_stream == 'false' }} + run: exit 1 runs-on: ubuntu-22.04 test-done: needs: - fate - srs + - asan - openssl-1-0-1k - openssl-1-0-2 - openssl-1-1-0h diff --git a/.gitignore b/.gitignore index da2f89a60dcba..9e56f800f8c67 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ /libavcodec/vulkan/*.c /libavfilter/vulkan/*.c /.*/ +/fate-suite From 415a7d54b300941fc7c5b03b4be676fb755f96d3 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 8 Jun 2025 09:59:52 -0400 Subject: [PATCH 03/24] CICD: Generate Patch for each PR (#22) --- .github/workflows/format-patch.sh | 93 ++++++++++++++++++++----------- .github/workflows/test.yml | 53 ++++++++++++++++++ 2 files changed, 114 insertions(+), 32 deletions(-) diff --git a/.github/workflows/format-patch.sh b/.github/workflows/format-patch.sh index b7fe8c560f8aa..ef67cd50c4cc8 100755 --- a/.github/workflows/format-patch.sh +++ b/.github/workflows/format-patch.sh @@ -9,7 +9,9 @@ if [[ $(jq --version 1>/dev/null 2>&1 && echo yes) != "yes" ]]; then exit 1 fi -if [ -z "$1" ]; then +PR_NUMBER="$1" +PATCH_FILE="$2" +if [ -z "$PR_NUMBER" ]; then echo "Please provide a PR link or number. For example: https://github.com/ossrs/ffmpeg-webrtc/pull/20" exit 1 fi @@ -29,65 +31,77 @@ echo "Fetching PR #$PR_NUMBER from $PR_URL" PR_DATA=$(curl -s "https://api.github.com/repos/ossrs/ffmpeg-webrtc/pulls/$PR_NUMBER") REPO_NAME=$(printf '%s' "$PR_DATA" | jq -r '.head.repo.full_name') BRANCH_NAME=$(printf '%s' "$PR_DATA" | jq -r '.head.ref') +echo "Repository: $REPO_NAME, Branch: $BRANCH_NAME" if [[ -z "$REPO_NAME" || -z "$BRANCH_NAME" ]]; then echo "Error: REPO_NAME or BRANCH_NAME is empty!" exit 1 fi -echo "Repository: $REPO_NAME, Branch: $BRANCH_NAME" PR_TITLE=$(printf '%s' "$PR_DATA" | jq -r '.title') PR_DESCRIPTION=$(printf '%s' "$PR_DATA" | jq -r '.body // ""') -if [[ -z "$PR_TITLE" ]]; then - echo "Error: PR title is empty!" - exit 1 -fi - echo "PR information:" echo "===================================================================" echo "$PR_TITLE" echo "$PR_DESCRIPTION" echo "===================================================================" echo "" +if [[ -z "$PR_TITLE" ]]; then + echo "Error: PR title is empty!" + exit 1 +fi -set -euxo pipefail - -git checkout workflows -echo "Switched to workflows branch." - +git checkout workflows && +echo "Switched to workflows branch." && git pull echo "Pulled latest changes from workflows branch." +if [[ $? -ne 0 ]]; then + echo "Failed to switch to workflows branch or pull latest changes." + exit 1 +fi -REMOTE_NAME=patch-tmp +REMOTE_NAME=patch-tmp && if git remote | grep -q "^$REMOTE_NAME$"; then git remote rm "$REMOTE_NAME" -fi -git remote add $REMOTE_NAME https://github.com/${REPO_NAME}.git -git fetch $REMOTE_NAME $BRANCH_NAME +fi && +git remote add $REMOTE_NAME https://github.com/${REPO_NAME}.git && +git fetch $REMOTE_NAME $BRANCH_NAME && echo "Fetch remote $REMOTE_NAME at $(git remote get-url $REMOTE_NAME)" +if [[ $? -ne 0 ]]; then + echo "Failed to fetch remote branch $BRANCH_NAME from $REMOTE_NAME." + exit 1 +fi -TMP_BRANCH=tmp-branch-for-patch-$PR_NUMBER +TMP_BRANCH=tmp-branch-for-patch-$PR_NUMBER && if git branch --list "$TMP_BRANCH" | grep -q "^..$TMP_BRANCH$"; then git branch -D "$TMP_BRANCH" -fi -git checkout -b $TMP_BRANCH $REMOTE_NAME/$BRANCH_NAME +fi && +git checkout -b $TMP_BRANCH $REMOTE_NAME/$BRANCH_NAME && echo "Checkout branch $TMP_BRANCH from $REMOTE_NAME/$BRANCH_NAME" +if [[ $? -ne 0 ]]; then + echo "Failed to checkout branch $TMP_BRANCH from $REMOTE_NAME/$BRANCH_NAME." + exit 1 +fi FIRST_AUTHOR_NAME=$(git log workflows..HEAD --reverse --format='%an' | head -n1) FIRST_AUTHOR_EMAIL=$(git log workflows..HEAD --reverse --format='%ae' | head -n1) echo "Author: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" +if [[ -z "$FIRST_AUTHOR_NAME" || -z "$FIRST_AUTHOR_EMAIL" ]]; then + echo "Error: Unable to determine the first author of the PR." + exit 1 +fi COAUTHORS=$(git log workflows..HEAD --format='Co-authored-by: %an <%ae>' |grep -v "$FIRST_AUTHOR_NAME" | sort -u) COAUTHOR_COUNT=$(echo "$COAUTHORS" | wc -l) -if [ "$COAUTHOR_COUNT" -gt 0 ]; then +if [[ "$COAUTHOR_COUNT" -gt 0 ]]; then echo "$COAUTHORS" fi COMMIT_MSG="$PR_TITLE" -if [ -n "$PR_DESCRIPTION" ]; then +if [[ -n "$PR_DESCRIPTION" ]]; then COMMIT_MSG="$COMMIT_MSG\n\n$PR_DESCRIPTION" fi -if [ "$COAUTHOR_COUNT" -gt 0 ]; then +if [[ "$COAUTHOR_COUNT" -gt 0 ]]; then COMMIT_MSG="$COMMIT_MSG\n" COMMIT_MSG="$COMMIT_MSG\n$COAUTHORS" fi @@ -95,26 +109,41 @@ fi echo "Commit information:" echo "Author: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" echo "===================================================================" -echo -e "$COMMIT_MSG" +echo -n -e "$COMMIT_MSG" echo "===================================================================" echo "" -git rebase workflows -git reset --soft workflows -git commit --author "$FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" -m "$(echo -e "$COMMIT_MSG")" -echo "Squashed commits into a single commit." +git rebase workflows && +git reset --soft workflows && +git commit --author "$FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" -m "$(echo -n -e "$COMMIT_MSG")" && +echo "Squashed commits into a single commit." && +if [[ $? -ne 0 ]]; then + echo "Failed to rebase or commit changes." + exit 1 +fi + +git branch -vv && git log -1 --pretty=format:"%an <%ae> %h %s" +if [[ $? -ne 0 ]]; then + echo "Failed to display branch information or last commit." + exit 1 +fi -PATCH_FILE="patch-$PR_NUMBER-$(date +%s).patch" -rm -f $PATCH_FILE -git format-patch -1 --stdout > $PATCH_FILE +if [[ -z "$PATCH_FILE" ]]; then + PATCH_FILE="whip-patch-$PR_NUMBER-$(date +%s).patch" +fi && +rm -f $PATCH_FILE && +git format-patch -1 --stdout > $PATCH_FILE && +echo "Created patch file: $PATCH_FILE" +if [[ $? -ne 0 ]]; then + echo "Failed to create patch file." + exit 1 +fi git checkout workflows #git br -D $TMP_BRANCH #echo "Removed temporary branch $TMP_BRANCH." -set +e +u +x +o pipefail - echo "" echo "Patch file created: $PATCH_FILE" echo "" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb604c11e9dee..1a3aefe3ab092 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -633,6 +633,58 @@ jobs: run: exit 1 runs-on: ubuntu-22.04 + generate-patch: + name: "Generate Patch" + if: ${{ github.event_name == 'pull_request' }} + steps: + # Checkout to workflows branch, make sure the base branch is available. + - name: Checkout repository with workflows branch + uses: actions/checkout@v4 + with: + ref: workflows + fetch-depth: 0 + - name: Try to checkout to workflows branch + run: | + set -euxo pipefail + git checkout workflows + git branch -vv + # Checkout to PR commit, use the lastest script. + - name: Checkout repository to PR commit + uses: actions/checkout@v4 + - name: Show Git Info + run: | + set -euxo pipefail + git branch -vv + echo "Repository: ${{ github.repository }}" + echo "Ref: ${{ github.ref }}" + echo "Event Name: ${{ github.event_name }}" + echo "Pull Request Number: ${{ github.event.pull_request.number }}" + - name: Install Dependencies + run: | + set -euxo pipefail + sudo apt-get update + sudo apt-get install -y jq + - name: Run Script + id: format_patch + run: | + set -euxo pipefail + + PR_NUMBER=${{ github.event.pull_request.number }} + PATCH_FILENAME="whip-patch-$PR_NUMBER-$(date +%s)" + echo "PR ID is ${{ github.event.pull_request.number }}" + echo "Patch file is $PATCH_FILENAME.patch" + + bash .github/workflows/format-patch.sh $PR_NUMBER "$PATCH_FILENAME.patch" + echo "patch_file=$PATCH_FILENAME" >> $GITHUB_OUTPUT + - name: Upload all patch files + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.format_patch.outputs.patch_file }} + path: | + whip-*.patch + retention-days: 90 + runs-on: ubuntu-22.04 + test-done: needs: - fate @@ -643,6 +695,7 @@ jobs: - openssl-1-1-0h - openssl-3-0 - openssl-latest + - generate-patch steps: - run: echo 'All done' runs-on: ubuntu-22.04 From b5e50c5cb77d053d42292967c3f897379d30a886 Mon Sep 17 00:00:00 2001 From: Winlin Date: Mon, 9 Jun 2025 09:49:55 -0400 Subject: [PATCH 04/24] CICD: Setup the author in git config. (#26) The patch is still generated by git config author. --- .github/workflows/format-patch.sh | 28 ++++++++++++++++++++++------ .github/workflows/test.yml | 7 +++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/.github/workflows/format-patch.sh b/.github/workflows/format-patch.sh index ef67cd50c4cc8..4539d9705f1fa 100755 --- a/.github/workflows/format-patch.sh +++ b/.github/workflows/format-patch.sh @@ -11,6 +11,7 @@ fi PR_NUMBER="$1" PATCH_FILE="$2" +TMP_BRANCH="$3" if [ -z "$PR_NUMBER" ]; then echo "Please provide a PR link or number. For example: https://github.com/ossrs/ffmpeg-webrtc/pull/20" exit 1 @@ -52,7 +53,7 @@ fi git checkout workflows && echo "Switched to workflows branch." && -git pull +git pull && echo "Pulled latest changes from workflows branch." if [[ $? -ne 0 ]]; then echo "Failed to switch to workflows branch or pull latest changes." @@ -71,7 +72,9 @@ if [[ $? -ne 0 ]]; then exit 1 fi -TMP_BRANCH=tmp-branch-for-patch-$PR_NUMBER && +if [[ -z "$TMP_BRANCH" ]]; then + TMP_BRANCH="tmp-branch-for-patch-$PR_NUMBER" +fi && if git branch --list "$TMP_BRANCH" | grep -q "^..$TMP_BRANCH$"; then git branch -D "$TMP_BRANCH" fi && @@ -90,7 +93,7 @@ if [[ -z "$FIRST_AUTHOR_NAME" || -z "$FIRST_AUTHOR_EMAIL" ]]; then exit 1 fi -COAUTHORS=$(git log workflows..HEAD --format='Co-authored-by: %an <%ae>' |grep -v "$FIRST_AUTHOR_NAME" | sort -u) +COAUTHORS=$(git log workflows..HEAD --format='Co-authored-by: %an <%ae>' | sort -u) COAUTHOR_COUNT=$(echo "$COAUTHORS" | wc -l) if [[ "$COAUTHOR_COUNT" -gt 0 ]]; then echo "$COAUTHORS" @@ -109,14 +112,27 @@ fi echo "Commit information:" echo "Author: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" echo "===================================================================" -echo -n -e "$COMMIT_MSG" +echo -e "$COMMIT_MSG" echo "===================================================================" echo "" +if [[ $(git config --list --local |grep 'user.name' >/dev/null 2>&1 && echo yes) != "yes" ]]; then + git config --local user.name "$FIRST_AUTHOR_NAME" +fi && +if [[ $(git config --list --local |grep 'user.email' >/dev/null 2>&1 && echo yes) != "yes" ]]; then + git config --local user.email "$FIRST_AUTHOR_EMAIL" +fi && +git config --list && +echo "Set local git user configuration to: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" +if [[ $? -ne 0 ]]; then + echo "Failed to set local git user configuration." + exit 1 +fi + git rebase workflows && git reset --soft workflows && -git commit --author "$FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" -m "$(echo -n -e "$COMMIT_MSG")" && -echo "Squashed commits into a single commit." && +git commit --author "$FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" -m "$(echo -e "$COMMIT_MSG")" && +echo "Squashed commits into a single commit." if [[ $? -ne 0 ]]; then echo "Failed to rebase or commit changes." exit 1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1a3aefe3ab092..ace9519b88258 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -671,11 +671,18 @@ jobs: PR_NUMBER=${{ github.event.pull_request.number }} PATCH_FILENAME="whip-patch-$PR_NUMBER-$(date +%s)" + TMP_BRANCH="tmp-branch-for-patch-$PR_NUMBER" echo "PR ID is ${{ github.event.pull_request.number }}" echo "Patch file is $PATCH_FILENAME.patch" + echo "Temporary branch is $TMP_BRANCH" bash .github/workflows/format-patch.sh $PR_NUMBER "$PATCH_FILENAME.patch" echo "patch_file=$PATCH_FILENAME" >> $GITHUB_OUTPUT + echo "temporary_branch=$TMP_BRANCH" >> $GITHUB_OUTPUT + - name: Show Branch Info + run: git show ${{ steps.format_patch.outputs.temporary_branch }} + - name: Show Patch File + run: cat ${{ steps.format_patch.outputs.patch_file }}.patch - name: Upload all patch files uses: actions/upload-artifact@v4 with: From cef2578716c8731ba835e34ac6b1f90d33023084 Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 9 Jun 2025 10:02:51 -0400 Subject: [PATCH 05/24] CICD: Do not present author as coauthor. --- .github/workflows/format-patch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format-patch.sh b/.github/workflows/format-patch.sh index 4539d9705f1fa..0b712a327a8cd 100755 --- a/.github/workflows/format-patch.sh +++ b/.github/workflows/format-patch.sh @@ -93,7 +93,7 @@ if [[ -z "$FIRST_AUTHOR_NAME" || -z "$FIRST_AUTHOR_EMAIL" ]]; then exit 1 fi -COAUTHORS=$(git log workflows..HEAD --format='Co-authored-by: %an <%ae>' | sort -u) +COAUTHORS=$(git log workflows..HEAD --format='Co-authored-by: %an <%ae>' |grep -v "$FIRST_AUTHOR_NAME" | sort -u) COAUTHOR_COUNT=$(echo "$COAUTHORS" | wc -l) if [[ "$COAUTHOR_COUNT" -gt 0 ]]; then echo "$COAUTHORS" From c402c53f44abaaaa0fe650760db0ff09b389a148 Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Tue, 10 Jun 2025 09:51:37 +0800 Subject: [PATCH 06/24] workflows/format-patch: fix git format-patch (#27) refer to https://www.ffmpeg.org/developer.html#Submitting-patches-1 add header "X-Unsent: 1" add email recipients Signed-off-by: Jack Lau --- .github/workflows/format-patch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format-patch.sh b/.github/workflows/format-patch.sh index 0b712a327a8cd..5e80d8c5cc0ea 100755 --- a/.github/workflows/format-patch.sh +++ b/.github/workflows/format-patch.sh @@ -149,7 +149,7 @@ if [[ -z "$PATCH_FILE" ]]; then PATCH_FILE="whip-patch-$PR_NUMBER-$(date +%s).patch" fi && rm -f $PATCH_FILE && -git format-patch -1 --stdout > $PATCH_FILE && +git format-patch --add-header "X-Unsent: 1" --to ffmpeg-devel@ffmpeg.org -1 --stdout > $PATCH_FILE && echo "Created patch file: $PATCH_FILE" if [[ $? -ne 0 ]]; then echo "Failed to create patch file." From 5d16c58c2bcfee51b19d07abbc0fa1ed257a0798 Mon Sep 17 00:00:00 2001 From: Winlin Date: Tue, 10 Jun 2025 10:34:08 -0400 Subject: [PATCH 07/24] CICD: Ignore files in .github for patch (#48) All files in the .github directory pertain to CI/CD and should be ignored; they should not be included in the patch. --- .github/{workflows => docker}/Dockerfile | 0 .github/scripts/format-patch.sh | 189 +++++++++++++++++++++++ .github/workflows/fate-cache.yml | 2 +- .github/workflows/format-patch.sh | 165 -------------------- .github/workflows/test.yml | 11 +- 5 files changed, 200 insertions(+), 167 deletions(-) rename .github/{workflows => docker}/Dockerfile (100%) create mode 100755 .github/scripts/format-patch.sh delete mode 100755 .github/workflows/format-patch.sh diff --git a/.github/workflows/Dockerfile b/.github/docker/Dockerfile similarity index 100% rename from .github/workflows/Dockerfile rename to .github/docker/Dockerfile diff --git a/.github/scripts/format-patch.sh b/.github/scripts/format-patch.sh new file mode 100755 index 0000000000000..3b56e701a652e --- /dev/null +++ b/.github/scripts/format-patch.sh @@ -0,0 +1,189 @@ +#!/bin/bash + +LOGPREFIX=">>" + +if [[ $(jq --version 1>/dev/null 2>&1 && echo yes) != "yes" ]]; then + echo "${LOGPREFIX} Tool jq is not installed. Please install it to parse JSON data. For example:" + echo "${LOGPREFIX} apt install jq" + echo "${LOGPREFIX} brew install jq" + echo "${LOGPREFIX} yum install jq" + echo "${LOGPREFIX} See https://github.com/jqlang/jq" + exit 1 +fi + +PR_NUMBER="$1" +PATCH_FILE="$2" +TMP_BRANCH="$3" +if [ -z "$PR_NUMBER" ]; then + echo "${LOGPREFIX} Please provide a PR link or number. For example: https://github.com/ossrs/ffmpeg-webrtc/pull/20" + exit 1 +fi + +if [[ "$1" =~ ^https://github.com/ossrs/ffmpeg-webrtc/pull/([0-9]+)$ ]]; then + PR_NUMBER="${BASH_REMATCH[1]}" +elif [[ "$1" =~ ^[0-9]+$ ]]; then + PR_NUMBER="$1" +else + echo "${LOGPREFIX} Invalid input format. Please provide a PR link or number. For example: https://github.com/ossrs/ffmpeg-webrtc/pull/20" + exit 1 +fi + +PR_URL="https://github.com/ossrs/ffmpeg-webrtc/pull/$PR_NUMBER" +echo "${LOGPREFIX} Fetching PR #$PR_NUMBER from $PR_URL" + +PR_DATA=$(curl -s "https://api.github.com/repos/ossrs/ffmpeg-webrtc/pulls/$PR_NUMBER") +REPO_NAME=$(printf '%s' "$PR_DATA" | jq -r '.head.repo.full_name') +BRANCH_NAME=$(printf '%s' "$PR_DATA" | jq -r '.head.ref') +echo "${LOGPREFIX} Repository: $REPO_NAME, Branch: $BRANCH_NAME" +if [[ -z "$REPO_NAME" || -z "$BRANCH_NAME" ]]; then + echo "${LOGPREFIX} Error: REPO_NAME or BRANCH_NAME is empty!" + exit 1 +fi + +PR_TITLE=$(printf '%s' "$PR_DATA" | jq -r '.title') +PR_DESCRIPTION=$(printf '%s' "$PR_DATA" | jq -r '.body // ""') +echo "${LOGPREFIX} PR information:" +echo "${LOGPREFIX} ===================================================================" +echo "${LOGPREFIX} $PR_TITLE" +echo "${LOGPREFIX} $PR_DESCRIPTION" +echo "${LOGPREFIX} ===================================================================" +echo "${LOGPREFIX} " +if [[ -z "$PR_TITLE" ]]; then + echo "${LOGPREFIX} Error: PR title is empty!" + exit 1 +fi + +git checkout workflows && +echo "${LOGPREFIX} Switched to workflows branch." && +git pull && +echo "${LOGPREFIX} Pulled latest changes from workflows branch." +if [[ $? -ne 0 ]]; then + echo "${LOGPREFIX} Failed to switch to workflows branch or pull latest changes." + exit 1 +fi + +REMOTE_NAME=patch-tmp && +if git remote | grep -q "^$REMOTE_NAME$"; then + git remote rm "$REMOTE_NAME" +fi && +git remote add $REMOTE_NAME https://github.com/${REPO_NAME}.git && +git fetch $REMOTE_NAME $BRANCH_NAME && +echo "${LOGPREFIX} Fetch remote $REMOTE_NAME at $(git remote get-url $REMOTE_NAME)" +if [[ $? -ne 0 ]]; then + echo "${LOGPREFIX} Failed to fetch remote branch $BRANCH_NAME from $REMOTE_NAME." + exit 1 +fi + +if [[ -z "$TMP_BRANCH" ]]; then + TMP_BRANCH="tmp-branch-for-patch-$PR_NUMBER" +fi && +if git branch --list "$TMP_BRANCH" | grep -q "^..$TMP_BRANCH$"; then + git branch -D "$TMP_BRANCH" +fi && +git checkout -b $TMP_BRANCH $REMOTE_NAME/$BRANCH_NAME && +echo "${LOGPREFIX} Checkout branch $TMP_BRANCH from $REMOTE_NAME/$BRANCH_NAME" +if [[ $? -ne 0 ]]; then + echo "${LOGPREFIX} Failed to checkout branch $TMP_BRANCH from $REMOTE_NAME/$BRANCH_NAME." + exit 1 +fi + +FIRST_AUTHOR_NAME=$(git log workflows..HEAD --reverse --format='%an' | head -n1) +FIRST_AUTHOR_EMAIL=$(git log workflows..HEAD --reverse --format='%ae' | head -n1) +echo "${LOGPREFIX} Author: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" +if [[ -z "$FIRST_AUTHOR_NAME" || -z "$FIRST_AUTHOR_EMAIL" ]]; then + echo "${LOGPREFIX} Error: Unable to determine the first author of the PR." + exit 1 +fi + +COAUTHORS=$(git log workflows..HEAD --format='Co-authored-by: %an <%ae>' |grep -v "$FIRST_AUTHOR_NAME" | sort -u) +COAUTHOR_COUNT=$(echo "$COAUTHORS" | wc -l) +if [[ "$COAUTHOR_COUNT" -gt 0 ]]; then + echo "${LOGPREFIX} $COAUTHORS" +fi + +COMMIT_MSG="$PR_TITLE" +if [[ -n "$PR_DESCRIPTION" ]]; then + COMMIT_MSG="$COMMIT_MSG\n\n$PR_DESCRIPTION" +fi + +if [[ "$COAUTHOR_COUNT" -gt 0 ]]; then + COMMIT_MSG="$COMMIT_MSG\n" + COMMIT_MSG="$COMMIT_MSG\n$COAUTHORS" +fi + +echo "${LOGPREFIX} Commit information:" +echo "${LOGPREFIX} Author: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" +echo "${LOGPREFIX} ===================================================================" +echo -e "$COMMIT_MSG" +echo "${LOGPREFIX} ===================================================================" +echo "${LOGPREFIX} " + +if [[ $(git config --list --local |grep 'user.name' >/dev/null 2>&1 && echo yes) != "yes" ]]; then + git config --local user.name "$FIRST_AUTHOR_NAME" +fi && +if [[ $(git config --list --local |grep 'user.email' >/dev/null 2>&1 && echo yes) != "yes" ]]; then + git config --local user.email "$FIRST_AUTHOR_EMAIL" +fi && +git config --list && +echo "${LOGPREFIX} Set local git user configuration to: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" +if [[ $? -ne 0 ]]; then + echo "${LOGPREFIX} Failed to set local git user configuration." + exit 1 +fi + +git rebase workflows && +git reset --soft workflows && +echo "${LOGPREFIX} Rebased onto workflows branch and reset to soft." +if [[ $? -ne 0 ]]; then + echo "${LOGPREFIX} Failed to rebase or reset changes." + exit 1 +fi + +git status && +git restore --staged .github && +git restore .github && +git status && +echo "${LOGPREFIX} Restored .github directory to the state of workflows branch." +if [[ $? -ne 0 ]]; then + echo "${LOGPREFIX} Failed to restore .github directory." + exit 1 +fi + +if [[ $(git status | grep 'nothing to commit, working tree clean' >/dev/null 2>&1 && echo yes) == "yes" ]]; then + echo "${LOGPREFIX} No changes to commit. Exiting." + git checkout workflows + exit 0 +fi + +git commit --author "$FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" -m "$(echo -e "$COMMIT_MSG")" && +echo "${LOGPREFIX} Squashed commits into a single commit." +if [[ $? -ne 0 ]]; then + echo "${LOGPREFIX} Failed to rebase or commit changes." + exit 1 +fi + +git branch -vv && +git log -1 --pretty=format:"%an <%ae> %h %s" +if [[ $? -ne 0 ]]; then + echo "${LOGPREFIX} Failed to display branch information or last commit." + exit 1 +fi + +if [[ -z "$PATCH_FILE" ]]; then + PATCH_FILE="whip-patch-$PR_NUMBER-$(date +%s).patch" +fi && +rm -f $PATCH_FILE && +git format-patch --add-header "X-Unsent: 1" --to ffmpeg-devel@ffmpeg.org -1 --stdout > $PATCH_FILE && +echo "${LOGPREFIX} Created patch file: $PATCH_FILE" +if [[ $? -ne 0 ]]; then + echo "${LOGPREFIX} Failed to create patch file." + exit 1 +fi + +git checkout workflows +#git br -D $TMP_BRANCH +#echo "${LOGPREFIX} Removed temporary branch $TMP_BRANCH." + +echo "${LOGPREFIX} " +echo "${LOGPREFIX} Patch file created: $PATCH_FILE" +echo "${LOGPREFIX} " diff --git a/.github/workflows/fate-cache.yml b/.github/workflows/fate-cache.yml index f8abc46ccf777..77ea32e3f3f4e 100644 --- a/.github/workflows/fate-cache.yml +++ b/.github/workflows/fate-cache.yml @@ -19,7 +19,7 @@ jobs: - name: Build FFmpeg Fate Cache run: | set -euxo pipefail - docker build -t ossrs/srs:ffmpeg-fate -f .github/workflows/Dockerfile . + docker build -t ossrs/srs:ffmpeg-fate -f .github/docker/Dockerfile . - name: Push FFmpeg Fate Cache run: | set -euxo pipefail diff --git a/.github/workflows/format-patch.sh b/.github/workflows/format-patch.sh deleted file mode 100755 index 5e80d8c5cc0ea..0000000000000 --- a/.github/workflows/format-patch.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/bash - -if [[ $(jq --version 1>/dev/null 2>&1 && echo yes) != "yes" ]]; then - echo "Tool jq is not installed. Please install it to parse JSON data. For example:" - echo " apt install jq" - echo " brew install jq" - echo " yum install jq" - echo "See https://github.com/jqlang/jq" - exit 1 -fi - -PR_NUMBER="$1" -PATCH_FILE="$2" -TMP_BRANCH="$3" -if [ -z "$PR_NUMBER" ]; then - echo "Please provide a PR link or number. For example: https://github.com/ossrs/ffmpeg-webrtc/pull/20" - exit 1 -fi - -if [[ "$1" =~ ^https://github.com/ossrs/ffmpeg-webrtc/pull/([0-9]+)$ ]]; then - PR_NUMBER="${BASH_REMATCH[1]}" -elif [[ "$1" =~ ^[0-9]+$ ]]; then - PR_NUMBER="$1" -else - echo "Invalid input format. Please provide a PR link or number. For example: https://github.com/ossrs/ffmpeg-webrtc/pull/20" - exit 1 -fi - -PR_URL="https://github.com/ossrs/ffmpeg-webrtc/pull/$PR_NUMBER" -echo "Fetching PR #$PR_NUMBER from $PR_URL" - -PR_DATA=$(curl -s "https://api.github.com/repos/ossrs/ffmpeg-webrtc/pulls/$PR_NUMBER") -REPO_NAME=$(printf '%s' "$PR_DATA" | jq -r '.head.repo.full_name') -BRANCH_NAME=$(printf '%s' "$PR_DATA" | jq -r '.head.ref') -echo "Repository: $REPO_NAME, Branch: $BRANCH_NAME" -if [[ -z "$REPO_NAME" || -z "$BRANCH_NAME" ]]; then - echo "Error: REPO_NAME or BRANCH_NAME is empty!" - exit 1 -fi - -PR_TITLE=$(printf '%s' "$PR_DATA" | jq -r '.title') -PR_DESCRIPTION=$(printf '%s' "$PR_DATA" | jq -r '.body // ""') -echo "PR information:" -echo "===================================================================" -echo "$PR_TITLE" -echo "$PR_DESCRIPTION" -echo "===================================================================" -echo "" -if [[ -z "$PR_TITLE" ]]; then - echo "Error: PR title is empty!" - exit 1 -fi - -git checkout workflows && -echo "Switched to workflows branch." && -git pull && -echo "Pulled latest changes from workflows branch." -if [[ $? -ne 0 ]]; then - echo "Failed to switch to workflows branch or pull latest changes." - exit 1 -fi - -REMOTE_NAME=patch-tmp && -if git remote | grep -q "^$REMOTE_NAME$"; then - git remote rm "$REMOTE_NAME" -fi && -git remote add $REMOTE_NAME https://github.com/${REPO_NAME}.git && -git fetch $REMOTE_NAME $BRANCH_NAME && -echo "Fetch remote $REMOTE_NAME at $(git remote get-url $REMOTE_NAME)" -if [[ $? -ne 0 ]]; then - echo "Failed to fetch remote branch $BRANCH_NAME from $REMOTE_NAME." - exit 1 -fi - -if [[ -z "$TMP_BRANCH" ]]; then - TMP_BRANCH="tmp-branch-for-patch-$PR_NUMBER" -fi && -if git branch --list "$TMP_BRANCH" | grep -q "^..$TMP_BRANCH$"; then - git branch -D "$TMP_BRANCH" -fi && -git checkout -b $TMP_BRANCH $REMOTE_NAME/$BRANCH_NAME && -echo "Checkout branch $TMP_BRANCH from $REMOTE_NAME/$BRANCH_NAME" -if [[ $? -ne 0 ]]; then - echo "Failed to checkout branch $TMP_BRANCH from $REMOTE_NAME/$BRANCH_NAME." - exit 1 -fi - -FIRST_AUTHOR_NAME=$(git log workflows..HEAD --reverse --format='%an' | head -n1) -FIRST_AUTHOR_EMAIL=$(git log workflows..HEAD --reverse --format='%ae' | head -n1) -echo "Author: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" -if [[ -z "$FIRST_AUTHOR_NAME" || -z "$FIRST_AUTHOR_EMAIL" ]]; then - echo "Error: Unable to determine the first author of the PR." - exit 1 -fi - -COAUTHORS=$(git log workflows..HEAD --format='Co-authored-by: %an <%ae>' |grep -v "$FIRST_AUTHOR_NAME" | sort -u) -COAUTHOR_COUNT=$(echo "$COAUTHORS" | wc -l) -if [[ "$COAUTHOR_COUNT" -gt 0 ]]; then - echo "$COAUTHORS" -fi - -COMMIT_MSG="$PR_TITLE" -if [[ -n "$PR_DESCRIPTION" ]]; then - COMMIT_MSG="$COMMIT_MSG\n\n$PR_DESCRIPTION" -fi - -if [[ "$COAUTHOR_COUNT" -gt 0 ]]; then - COMMIT_MSG="$COMMIT_MSG\n" - COMMIT_MSG="$COMMIT_MSG\n$COAUTHORS" -fi - -echo "Commit information:" -echo "Author: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" -echo "===================================================================" -echo -e "$COMMIT_MSG" -echo "===================================================================" -echo "" - -if [[ $(git config --list --local |grep 'user.name' >/dev/null 2>&1 && echo yes) != "yes" ]]; then - git config --local user.name "$FIRST_AUTHOR_NAME" -fi && -if [[ $(git config --list --local |grep 'user.email' >/dev/null 2>&1 && echo yes) != "yes" ]]; then - git config --local user.email "$FIRST_AUTHOR_EMAIL" -fi && -git config --list && -echo "Set local git user configuration to: $FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" -if [[ $? -ne 0 ]]; then - echo "Failed to set local git user configuration." - exit 1 -fi - -git rebase workflows && -git reset --soft workflows && -git commit --author "$FIRST_AUTHOR_NAME <$FIRST_AUTHOR_EMAIL>" -m "$(echo -e "$COMMIT_MSG")" && -echo "Squashed commits into a single commit." -if [[ $? -ne 0 ]]; then - echo "Failed to rebase or commit changes." - exit 1 -fi - -git branch -vv && -git log -1 --pretty=format:"%an <%ae> %h %s" -if [[ $? -ne 0 ]]; then - echo "Failed to display branch information or last commit." - exit 1 -fi - -if [[ -z "$PATCH_FILE" ]]; then - PATCH_FILE="whip-patch-$PR_NUMBER-$(date +%s).patch" -fi && -rm -f $PATCH_FILE && -git format-patch --add-header "X-Unsent: 1" --to ffmpeg-devel@ffmpeg.org -1 --stdout > $PATCH_FILE && -echo "Created patch file: $PATCH_FILE" -if [[ $? -ne 0 ]]; then - echo "Failed to create patch file." - exit 1 -fi - -git checkout workflows -#git br -D $TMP_BRANCH -#echo "Removed temporary branch $TMP_BRANCH." - -echo "" -echo "Patch file created: $PATCH_FILE" -echo "" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ace9519b88258..397d9c69390a6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -676,14 +676,23 @@ jobs: echo "Patch file is $PATCH_FILENAME.patch" echo "Temporary branch is $TMP_BRANCH" - bash .github/workflows/format-patch.sh $PR_NUMBER "$PATCH_FILENAME.patch" + bash .github/scripts/format-patch.sh $PR_NUMBER "$PATCH_FILENAME.patch" echo "patch_file=$PATCH_FILENAME" >> $GITHUB_OUTPUT echo "temporary_branch=$TMP_BRANCH" >> $GITHUB_OUTPUT + + if [[ -f "$PATCH_FILENAME.patch" ]]; then + echo "has_patch=true" >> $GITHUB_OUTPUT + else + echo "has_patch=false" >> $GITHUB_OUTPUT + fi - name: Show Branch Info + if: ${{ steps.format_patch.outputs.has_patch == 'true' }} run: git show ${{ steps.format_patch.outputs.temporary_branch }} - name: Show Patch File + if: ${{ steps.format_patch.outputs.has_patch == 'true' }} run: cat ${{ steps.format_patch.outputs.patch_file }}.patch - name: Upload all patch files + if: ${{ steps.format_patch.outputs.has_patch == 'true' }} uses: actions/upload-artifact@v4 with: name: ${{ steps.format_patch.outputs.patch_file }} From 8da61cd099a41bde1cce2406521f6aaf763f0ed5 Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Sat, 21 Jun 2025 10:19:44 +0800 Subject: [PATCH 08/24] Workflows: add pion and janus test (#53) This PR refer to https://github.com/ossrs/ffmpeg-webrtc/discussions/39 and https://github.com/ossrs/ffmpeg-webrtc/discussions/38 --------- Signed-off-by: Jack Lau --- .github/workflows/test.yml | 129 +++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 397d9c69390a6..1a8ced2650028 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -152,6 +152,133 @@ jobs: run: exit 1 runs-on: ubuntu-22.04 + pion: + name: "FFmpeg with Pion" + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config jq libssl-dev libopus-dev libx264-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ + --enable-libx264 --enable-gpl --enable-libopus + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + - name: Verify Go version + run: go version + - name: Start Pion + run: | + set -euxo pipefail + git clone https://github.com/pion/webrtc.git + cd webrtc/examples/whip-whep + go run *.go & + - name: Streaming with FFmpeg + run: | + set -euxo pipefail + nohup ./ffmpeg -t 30 -re -f lavfi -i testsrc=size=1280x720 -f lavfi -i sine=frequency=440 -pix_fmt yuv420p \ + -vcodec libx264 -profile:v baseline -r 25 -g 50 -acodec libopus -ar 48000 -ac 2 \ + -f whip -authorization "seanTest" "http://localhost:8080/whip" \ + 1>ffstdout.log 2>ffstderr.log & + - name: Stop FFmpeg normally + run: | + pkill -SIGINT ffmpeg && sleep 3 || + echo "FFmpeg process not found or already stopped." + - name: Show FFmpeg Stdout Log + run: cat ffstdout.log + - name: Show FFmpeg Stderr Log + run: cat ffstderr.log + - name: Check FFmpeg Exit Log + run: | + set -euxo pipefail + cat ffstderr.log |grep 'Exiting normally' && exit 0 + echo "Exiting normally not found in ffstderr.log" && exit 1 + - name: Check Stream Existence + if: ${{ steps.streaming.outputs.has_stream == 'false' }} + run: exit 1 + runs-on: ubuntu-22.04 + + janus: + name: "FFmpeg with Janus" + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build FFmpeg + run: | + set -euxo pipefail + + # Install dependencies + sudo apt-get update + sudo apt-get install -y nasm pkg-config jq libssl-dev libopus-dev libx264-dev + + # Build FFmpeg with WebRTC support + ./configure --enable-muxer=whip --enable-openssl --enable-version3 \ + --enable-libx264 --enable-gpl --enable-libopus + make -j$(nproc) + ./ffmpeg -version && ./ffmpeg -muxers 2>/dev/null |grep whip + - name: Start Janus + run: | + set -euxo pipefail + git clone https://github.com/winlinvip/janus-docker.git + (cd janus-docker && + ip=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}') && + sed -i "s|\(^[[:blank:]]*nat_1_1_mapping *=\).*|\1\"$ip\"|g" janus.jcfg && + docker run --rm -d -p 8081:8080 -p 8188:8188 -p 8443:8443 -p 20000-20010:20000-20010/udp \ + -v $(pwd)/janus.jcfg:/usr/local/etc/janus/janus.jcfg \ + -v $(pwd)/janus.plugin.videoroom.jcfg:/usr/local/etc/janus/janus.plugin.videoroom.jcfg \ + -v $(pwd)/janus.transport.http.jcfg:/usr/local/etc/janus/janus.transport.http.jcfg \ + -v $(pwd)/janus.transport.websockets.jcfg:/usr/local/etc/janus/janus.transport.websockets.jcfg \ + -v $(pwd)/videoroomtest.js:/usr/local/share/janus/demos/videoroomtest.js \ + ossrs/janus:v1.0.12) + + git clone https://github.com/meetecho/simple-whip-server.git + cd simple-whip-server + git checkout bd2d98898b9842bfc329443b46bcc906aab857aa + npm install + npm run build + npm run start & + + - name: Streaming with FFmpeg + run: | + set -euxo pipefail + curl -H 'Content-Type: application/json' -d '{"id": "abc123", "room": 2345}' \ + http://localhost:7080/whip/create + nohup ./ffmpeg -t 30 -re -f lavfi -i testsrc=size=1280x720 -f lavfi -i sine=frequency=440 -pix_fmt yuv420p \ + -vcodec libx264 -profile:v baseline -r 25 -g 50 -acodec libopus -ar 48000 -ac 2 \ + -f whip 'http://localhost:7080/whip/endpoint/abc123' \ + 1>ffstdout.log 2>ffstderr.log & + - name: Stop FFmpeg normally + run: | + pkill -SIGINT ffmpeg && sleep 3 || + echo "FFmpeg process not found or already stopped." + - name: Show FFmpeg Stdout Log + run: cat ffstdout.log + - name: Show FFmpeg Stderr Log + run: cat ffstderr.log + - name: Check FFmpeg Exit Log + run: | + set -euxo pipefail + cat ffstderr.log |grep 'Exiting normally' && exit 0 + echo "Exiting normally not found in ffstderr.log" && exit 1 + - name: Check Stream Existence + if: ${{ steps.streaming.outputs.has_stream == 'false' }} + run: exit 1 + - name: Setup tmate session + if: ${{ failure() }} + uses: mxschmitt/action-tmate@v3 + runs-on: ubuntu-22.04 + asan: name: "FFmpeg with Asan" needs: build @@ -705,6 +832,8 @@ jobs: needs: - fate - srs + - pion + - janus - asan - openssl-1-0-1k - openssl-1-0-2 From cfdc986596f1e0ea7bc00cdc7aadd29cda50e544 Mon Sep 17 00:00:00 2001 From: winlin Date: Fri, 27 Jun 2025 07:10:37 -0400 Subject: [PATCH 09/24] AI: Add ignore for augment code. --- .augmentignore | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .augmentignore diff --git a/.augmentignore b/.augmentignore new file mode 100644 index 0000000000000..0ec86037100e4 --- /dev/null +++ b/.augmentignore @@ -0,0 +1,25 @@ + +# Build artifacts +**/objs/** +**/build/** +**/*.o +**/*.a +**/*.so +**/*.dylib +**/*.d + +# IDE files +**/.idea/** +**/.vscode/** +**/.run/** + +# Generated files +**/.tmp/** +**/fate-suite/** +**/*.flv +**/*.mp4 +**/*.ts + +# Other files. +**/tools/** +**/tests/** From 5a992b3e1e52f9112911b27eb38e444f1b190323 Mon Sep 17 00:00:00 2001 From: winlin Date: Fri, 27 Jun 2025 07:59:32 -0400 Subject: [PATCH 10/24] AI: Add guideline for augment code. --- .augment-guidelines | 62 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .augment-guidelines diff --git a/.augment-guidelines b/.augment-guidelines new file mode 100644 index 0000000000000..79e2b5b32f305 --- /dev/null +++ b/.augment-guidelines @@ -0,0 +1,62 @@ +# Augment Guidelines for FFmpeg Repository + +project: + name: "FFmpeg" + description: | + A complete, cross-platform solution to record, convert and stream audio and video. + type: "media" + +architecture: + overview: | + FFmpeg is organized into several core libraries that handle different aspects of + multimedia processing. The codebase follows a modular design where each library + can be used independently or together. FFmpeg also provides command-line tools + built on top of these libraries. + + key_directories: + - path: "libavcodec" + description: | + Implements encoders and decoders for audio/video codecs and bitstream processing + - path: "libavdevice" + description: | + Provides abstraction for accessing capture and playback devices + - path: "libavfilter" + description: | + Implements media filtering framework for processing audio and video + - path: "libavformat" + description: | + Handles container formats, muxing/demuxing, and I/O protocols + - path: "libavutil" + description: | + Contains utility functions, data structures, and common components shared across + libraries + - path: "libswresample" + description: | + Implements audio mixing and resampling routines + - path: "tests" + description: | + Contains test suites and validation tools for FFmpeg functionality + +components: + - name: "WHIP" + description: | + WebRTC-HTTP Ingestion Protocol implementation for low-latency streaming. Handles + SDP exchange, ICE connectivity, DTLS handshake, SRTP encryption, and RTP + packetization for WebRTC streaming. + related_files: + - path: "libavformat/whip.c" + description: | + Core implementation of the WHIP protocol, including SDP offer/answer exchange, + ICE connectivity, DTLS handshake setup, and SRTP encryption for RTP packets + - path: "libavformat/tls.h" + description: | + Header defining the DTLS interface used by WHIP for secure communication, + including functions for certificate handling and DTLS state management + - path: "libavformat/tls.c" + description: | + Common DTLS implementation shared across different SSL backends, providing + UDP socket setup for DTLS connections + - path: "libavformat/tls_openssl.c" + description: | + OpenSSL-specific implementation of DTLS functionality, including handshake + procedures and SRTP key material export From a4a994b35a1aa9108bb05769b1fd4cb24889fe22 Mon Sep 17 00:00:00 2001 From: Sergio Garcia Murillo Date: Tue, 10 Jun 2025 14:26:55 +0200 Subject: [PATCH 11/24] Implement NACK+RTX suppport --- libavformat/whip.c | 163 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 3 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index 5fdbd6949d541..d67e01f22bae3 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -114,6 +114,7 @@ /* Referring to Chrome's definition of RTP payload types. */ #define WHIP_RTP_PAYLOAD_TYPE_H264 106 #define WHIP_RTP_PAYLOAD_TYPE_OPUS 111 +#define WHIP_RTP_PAYLOAD_TYPE_RTX 105 /** * The STUN message header, which is 20 bytes long, comprises the @@ -150,6 +151,11 @@ #define WHIP_SDP_SESSION_ID "4489045141692799359" #define WHIP_SDP_CREATOR_IP "127.0.0.1" +/** + * Retransmission / NACK support +*/ +#define HISTORY_SIZE_DEFAULT 512 + /* Calculate the elapsed time from starttime to endtime in milliseconds. */ #define ELAPSED(starttime, endtime) ((int)(endtime - starttime) / 1000) @@ -193,6 +199,15 @@ enum WHIPState { WHIP_STATE_FAILED, }; +typedef struct RtpHistoryItem { + /* original RTP seq */ + uint16_t seq; + /* length in bytes */ + int size; + /* malloc-ed copy */ + uint8_t* pkt; +} RtpHistoryItem; + typedef struct WHIPContext { AVClass *av_class; @@ -303,6 +318,15 @@ typedef struct WHIPContext { /* The certificate and private key used for DTLS handshake. */ char* cert_file; char* key_file; + + /* RTX / NACK */ + uint8_t rtx_payload_type; + uint32_t video_rtx_ssrc; + uint16_t rtx_seq; + int history_size; + RtpHistoryItem * history; /* ring buffer */ + int hist_head; + int enable_nack_rtx; } WHIPContext; /** @@ -615,6 +639,17 @@ static int generate_sdp_offer(AVFormatContext *s) whip->audio_payload_type = WHIP_RTP_PAYLOAD_TYPE_OPUS; whip->video_payload_type = WHIP_RTP_PAYLOAD_TYPE_H264; + /* RTX / NACK init */ + whip->rtx_payload_type = WHIP_RTP_PAYLOAD_TYPE_RTX; + whip->video_rtx_ssrc = av_lfg_get(&whip->rnd); + whip->rtx_seq = 0; + whip->hist_head = 0; + whip->history_size = FFMAX(64, whip->history_size); + whip->history = av_calloc(whip->history_size, sizeof(*whip->history)); + if (!whip->history) + return AVERROR(ENOMEM); + whip->enable_nack_rtx = 1; + av_bprintf(&bp, "" "v=0\r\n" "o=FFmpeg %s 2 IN IP4 %s\r\n" @@ -666,7 +701,7 @@ static int generate_sdp_offer(AVFormatContext *s) } av_bprintf(&bp, "" - "m=video 9 UDP/TLS/RTP/SAVPF %u\r\n" + "m=video 9 UDP/TLS/RTP/SAVPF %u %u\r\n" "c=IN IP4 0.0.0.0\r\n" "a=ice-ufrag:%s\r\n" "a=ice-pwd:%s\r\n" @@ -679,9 +714,16 @@ static int generate_sdp_offer(AVFormatContext *s) "a=rtcp-rsize\r\n" "a=rtpmap:%u %s/90000\r\n" "a=fmtp:%u level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=%02x%02x%02x\r\n" + "a=rtcp-fb:%u nack\r\n" + "a=rtpmap:%u rtx/90000\r\n" + "a=fmtp:%u apt=%u\r\n" + "a=ssrc:%u cname:FFmpeg\r\n" + "a=ssrc:%u msid:FFmpeg video\r\n" "a=ssrc:%u cname:FFmpeg\r\n" - "a=ssrc:%u msid:FFmpeg video\r\n", + "a=ssrc:%u msid:FFmpeg video\r\n" + "a=ssrc-group:FID %u %u\r\n", whip->video_payload_type, + whip->rtx_payload_type, whip->ice_ufrag_local, whip->ice_pwd_local, whip->dtls_fingerprint, @@ -691,8 +733,16 @@ static int generate_sdp_offer(AVFormatContext *s) profile, profile_iop, level, + whip->video_payload_type, + whip->rtx_payload_type, + whip->rtx_payload_type, + whip->video_payload_type, + whip->video_ssrc, whip->video_ssrc, - whip->video_ssrc); + whip->video_rtx_ssrc, + whip->video_rtx_ssrc, + whip->video_ssrc, + whip->video_rtx_ssrc); } if (!av_bprint_is_complete(&bp)) { @@ -1429,6 +1479,37 @@ static int setup_srtp(AVFormatContext *s) return ret; } + +/** + * RTX history helpers + */ + static void rtp_history_store(WHIPContext *whip, const uint8_t *pkt, int size) +{ + int pos = whip->hist_head % whip->history_size; + RtpHistoryItem * it = &whip->history[pos]; + /* free older entry */ + av_free(it->pkt); + it->pkt = av_malloc(size); + if (!it->pkt) + return; + + memcpy(it->pkt, pkt, size); + it->size = size; + it->seq = AV_RB16(pkt + 2); + + whip->hist_head++; +} + +static const RtpHistoryItem* rtp_history_find(const WHIPContext *whip, uint16_t seq) +{ + for (int i = 0; i < whip->history_size; i++) { + const RtpHistoryItem * it = &whip->history[i]; + if (it->pkt && it->seq == seq) + return it; + } + return NULL; +} + /** * Callback triggered by the RTP muxer when it creates and sends out an RTP packet. * @@ -1465,6 +1546,10 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size) return 0; } + /* Store only ORIGINAL video packets (non-RTX, non-RTCP) */ + if (!is_rtcp && is_video) + rtp_history_store(whip, buf, buf_size); + ret = ffurl_write(whip->udp, whip->buf, cipher_size); if (ret < 0) { av_log(whip, AV_LOG_ERROR, "WHIP: Failed to write packet=%dB, ret=%d\n", cipher_size, ret); @@ -1473,6 +1558,45 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size) return ret; } +/** + * Build and send a single RTX packet +*/ +static int send_rtx_packet(AVFormatContext *s, const uint8_t * orig_pkt, int orig_size) +{ + WHIPContext * whip = s->priv_data; + int new_size, cipher_size; + /* skip if no RTX PT configured */ + if (!whip->enable_nack_rtx) + return 0; + + /* allocate new buffer: header + 2 + payload */ + if (orig_size + 2 > sizeof(whip->buf)) + return 0; + + memcpy(whip->buf, orig_pkt, orig_size); + + uint8_t * hdr = whip->buf; + uint16_t orig_seq = AV_RB16(hdr + 2); + + /* rewrite header */ + hdr[1] = (hdr[1] & 0x80) | whip->rtx_payload_type; /* keep M bit */ + AV_WB16(hdr + 2, whip->rtx_seq++); + AV_WB32(hdr + 8, whip->video_rtx_ssrc); + + /* shift payload 2 bytes */ + memmove(hdr + 12 + 2, hdr + 12, orig_size - 12); + AV_WB16(hdr + 12, orig_seq); + + new_size = orig_size + 2; + + /* Encrypt by SRTP and send out. */ + cipher_size = ff_srtp_encrypt(&whip->srtp_video_send, whip->buf, new_size, whip->buf, sizeof(whip->buf)); + if (cipher_size <= 0 || cipher_size < new_size) { + av_log(whip, AV_LOG_WARNING, "WHIP: Failed to encrypt packet=%dB, cipher=%dB\n", new_size, cipher_size); + return 0; + } + return ffurl_write(whip->udp, whip->buf, cipher_size); +} /** * Creates dedicated RTP muxers for each stream in the AVFormatContext to build RTP @@ -1795,6 +1919,38 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) goto end; } } + /* Handle RTCP NACK ( RTPFB / FMT=1 ) */ + if (media_is_rtcp(whip->buf, ret)) { + int ptr = 0; + while (ptr + 4 <= ret) { + uint8_t pt = whip->buf[ptr + 1]; + int len = (AV_RB16(&whip->buf[ptr + 2]) + 1) * 4; + if (ptr + len > ret) break; + + if (pt == 205) { /* RTPFB */ + uint8_t fmt = (whip->buf[ptr] & 0x1f); + if (fmt == 1 && len >= 12) { + uint16_t pid = AV_RB16(&whip->buf[ptr + 12 - 4]); + uint16_t blp = AV_RB16(&whip->buf[ptr + 14 - 4]); + + /* retransmit pid + any bit set in blp */ + for (int bit = -1; bit < 16; bit++) { + uint16_t seq = (bit < 0) ? pid : pid + bit + 1; + if (bit >= 0 && !(blp & (1 << bit))) + continue; + + const RtpHistoryItem * it = rtp_history_find(whip, seq); + if (it) + send_rtx_packet(s, it->pkt, it->size); + + } + } + + } + ptr += len; + } + + } } else if (ret != AVERROR(EAGAIN)) { av_log(whip, AV_LOG_ERROR, "WHIP: Failed to read from UDP socket\n"); goto end; @@ -1898,6 +2054,7 @@ static const AVOption options[] = { { "authorization", "The optional Bearer token for WHIP Authorization", OFFSET(authorization), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC }, { "cert_file", "The optional certificate file path for DTLS", OFFSET(cert_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC }, { "key_file", "The optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC }, + { "rtx_history", "Packet history size", OFFSET(history_size), AV_OPT_TYPE_INT, { .i64 = HISTORY_SIZE_DEFAULT }, 64, 2048, ENC }, { NULL }, }; From 6859e251bcf897821fd0f5b85ef9553f7f10b933 Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Tue, 10 Jun 2025 22:02:33 +0800 Subject: [PATCH 12/24] avformat/whip: remove extra blank lines Signed-off-by: Jack Lau --- libavformat/whip.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index d67e01f22bae3..7e65008231fcb 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -1942,10 +1942,8 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) const RtpHistoryItem * it = rtp_history_find(whip, seq); if (it) send_rtx_packet(s, it->pkt, it->size); - } } - } ptr += len; } From a2df36947cd896138113f546e1eed7e24ff6022d Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Thu, 12 Jun 2025 18:03:06 +0800 Subject: [PATCH 13/24] avformat/whip: properly handle encrypted SRTCP NACK pakcet decrypt the SRTCP pakcet, get the right pid and blp data add more comments Signed-off-by: Jack Lau --- libavformat/whip.c | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index 7e65008231fcb..1aad4f321f1ae 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -154,7 +154,7 @@ /** * Retransmission / NACK support */ -#define HISTORY_SIZE_DEFAULT 512 +#define HISTORY_SIZE_DEFAULT 1024 /* Calculate the elapsed time from starttime to endtime in milliseconds. */ #define ELAPSED(starttime, endtime) ((int)(endtime - starttime) / 1000) @@ -1919,19 +1919,40 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) goto end; } } - /* Handle RTCP NACK ( RTPFB / FMT=1 ) */ + /** + * Handle RTCP NACK + * Refer to RFC 4585, Section 6.2.1 + * The Generic NACK message is identified by PT=RTPFB and FMT=1. + */ if (media_is_rtcp(whip->buf, ret)) { int ptr = 0; while (ptr + 4 <= ret) { uint8_t pt = whip->buf[ptr + 1]; + /** + * Refer to RFC 3550, Section 6.4.1. + * The length of this RTCP packet in 32-bit words minus one, + * including the header and any padding. + */ int len = (AV_RB16(&whip->buf[ptr + 2]) + 1) * 4; if (ptr + len > ret) break; - if (pt == 205) { /* RTPFB */ + if (pt == 205) { /* PT=RTPFB */ uint8_t fmt = (whip->buf[ptr] & 0x1f); - if (fmt == 1 && len >= 12) { - uint16_t pid = AV_RB16(&whip->buf[ptr + 12 - 4]); - uint16_t blp = AV_RB16(&whip->buf[ptr + 14 - 4]); + if (fmt == 1 && len >= 12) { /* FMT=1 */ + /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */ + int srtcp_len = len + 4 + 10; + int ret = ff_srtp_decrypt(&whip->srtp_recv, whip->buf, &srtcp_len); + if (ret < 0) { + av_log(whip, AV_LOG_ERROR, "WHIP: SRTCP decrypt failed: %d\n", ret); + // packet is invalid or authentication failed + break; + } + /** + * See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1 + * TODO: Handle multi NACK in boudary packets. + */ + uint16_t pid = AV_RB16(&whip->buf[ptr + 12]); + uint16_t blp = AV_RB16(&whip->buf[ptr + 14]); /* retransmit pid + any bit set in blp */ for (int bit = -1; bit < 16; bit++) { @@ -1939,9 +1960,12 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) if (bit >= 0 && !(blp & (1 << bit))) continue; - const RtpHistoryItem * it = rtp_history_find(whip, seq); - if (it) - send_rtx_packet(s, it->pkt, it->size); + const RtpHistoryItem * it = rtp_history_find(whip, seq); + if (it) { + send_rtx_packet(s, it->pkt, it->size); + av_log(whip, AV_LOG_INFO, "WHIP: NACK packet found: size: %d, seq=%d, blp=%d\n", it->size, seq, blp); + } else + av_log(whip, AV_LOG_INFO, "WHIP: NACK packet, seq=%d, blp=%d, not found\n", seq, blp); } } } From e454868e30d72e088f33974eedcca185d154084d Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Thu, 12 Jun 2025 18:11:33 +0800 Subject: [PATCH 14/24] avformat/whip: fix typo Signed-off-by: Jack Lau --- libavformat/whip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index 1aad4f321f1ae..7e1920c2ab415 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -1949,7 +1949,7 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) } /** * See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1 - * TODO: Handle multi NACK in boudary packets. + * TODO: Handle multi NACKs in bundled packet. */ uint16_t pid = AV_RB16(&whip->buf[ptr + 12]); uint16_t blp = AV_RB16(&whip->buf[ptr + 14]); From 1a93f48093096365080690f7c6854b6eaf2702c9 Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Fri, 13 Jun 2025 11:08:34 +0800 Subject: [PATCH 15/24] avformat/whip: fix whip->hist_head overflow Signed-off-by: Jack Lau --- libavformat/whip.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index 7e1920c2ab415..078aa3cc6686c 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -1497,7 +1497,7 @@ static int setup_srtp(AVFormatContext *s) it->size = size; it->seq = AV_RB16(pkt + 2); - whip->hist_head++; + whip->hist_head = ++pos; } static const RtpHistoryItem* rtp_history_find(const WHIPContext *whip, uint16_t seq) @@ -1559,8 +1559,9 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size) return ret; } /** + * See https://datatracker.ietf.org/doc/html/rfc4588 * Build and send a single RTX packet -*/ + */ static int send_rtx_packet(AVFormatContext *s, const uint8_t * orig_pkt, int orig_size) { WHIPContext * whip = s->priv_data; @@ -1922,7 +1923,8 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) /** * Handle RTCP NACK * Refer to RFC 4585, Section 6.2.1 - * The Generic NACK message is identified by PT=RTPFB and FMT=1. + * The Generic NACK message is identified by PT=RTPFB and FMT=1. + * TODO: disable retransmisstion when "-tune zerolatency" */ if (media_is_rtcp(whip->buf, ret)) { int ptr = 0; From 691ab83900c71021c6d6fc516b9c599d7150899e Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Sat, 14 Jun 2025 20:32:34 +0800 Subject: [PATCH 16/24] avformat/whip: add srtp setup for video rtx unique ssrc need to use unique srtp context, otherwise the server couldn't decrypted the rtx packet. move the "a=ssrc-group:FID" on top of SSRCs Signed-off-by: Jack Lau --- libavformat/whip.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index 078aa3cc6686c..3fb036649eb97 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -294,6 +294,7 @@ typedef struct WHIPContext { /* The SRTP send context, to encrypt outgoing packets. */ SRTPContext srtp_audio_send; SRTPContext srtp_video_send; + SRTPContext srtp_video_rtx_send; SRTPContext srtp_rtcp_send; /* The SRTP receive context, to decrypt incoming packets. */ SRTPContext srtp_recv; @@ -717,11 +718,11 @@ static int generate_sdp_offer(AVFormatContext *s) "a=rtcp-fb:%u nack\r\n" "a=rtpmap:%u rtx/90000\r\n" "a=fmtp:%u apt=%u\r\n" + "a=ssrc-group:FID %u %u\r\n" "a=ssrc:%u cname:FFmpeg\r\n" "a=ssrc:%u msid:FFmpeg video\r\n" "a=ssrc:%u cname:FFmpeg\r\n" - "a=ssrc:%u msid:FFmpeg video\r\n" - "a=ssrc-group:FID %u %u\r\n", + "a=ssrc:%u msid:FFmpeg video\r\n", whip->video_payload_type, whip->rtx_payload_type, whip->ice_ufrag_local, @@ -738,10 +739,10 @@ static int generate_sdp_offer(AVFormatContext *s) whip->rtx_payload_type, whip->video_payload_type, whip->video_ssrc, - whip->video_ssrc, - whip->video_rtx_ssrc, whip->video_rtx_ssrc, whip->video_ssrc, + whip->video_ssrc, + whip->video_rtx_ssrc, whip->video_rtx_ssrc); } @@ -1450,6 +1451,12 @@ static int setup_srtp(AVFormatContext *s) goto end; } + ret = ff_srtp_set_crypto(&whip->srtp_video_rtx_send, suite, buf); + if (ret < 0) { + av_log(whip, AV_LOG_ERROR, "WHIP: Failed to set crypto for video rtx send\n"); + goto end; + } + ret = ff_srtp_set_crypto(&whip->srtp_rtcp_send, suite, buf); if (ret < 0) { av_log(whip, AV_LOG_ERROR, "Failed to set crypto for rtcp send\n"); @@ -1591,7 +1598,7 @@ static int send_rtx_packet(AVFormatContext *s, const uint8_t * orig_pkt, int ori new_size = orig_size + 2; /* Encrypt by SRTP and send out. */ - cipher_size = ff_srtp_encrypt(&whip->srtp_video_send, whip->buf, new_size, whip->buf, sizeof(whip->buf)); + cipher_size = ff_srtp_encrypt(&whip->srtp_video_rtx_send, whip->buf, new_size, whip->buf, sizeof(whip->buf)); if (cipher_size <= 0 || cipher_size < new_size) { av_log(whip, AV_LOG_WARNING, "WHIP: Failed to encrypt packet=%dB, cipher=%dB\n", new_size, cipher_size); return 0; From 93c166c964cf8c019bfcce6018b78f94621a067b Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Sun, 15 Jun 2025 10:55:58 +0800 Subject: [PATCH 17/24] avfotmat/whip: add support to handle multi NACK in a bundled packet Signed-off-by: Jack Lau --- libavformat/whip.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index 3fb036649eb97..9bb7a798ca9e9 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -154,7 +154,7 @@ /** * Retransmission / NACK support */ -#define HISTORY_SIZE_DEFAULT 1024 +#define HISTORY_SIZE_DEFAULT 4096 /* Calculate the elapsed time from starttime to endtime in milliseconds. */ #define ELAPSED(starttime, endtime) ((int)(endtime - starttime) / 1000) @@ -1937,6 +1937,7 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) int ptr = 0; while (ptr + 4 <= ret) { uint8_t pt = whip->buf[ptr + 1]; + uint8_t fmt = (whip->buf[ptr] & 0x1f); /** * Refer to RFC 3550, Section 6.4.1. * The length of this RTCP packet in 32-bit words minus one, @@ -1945,9 +1946,8 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) int len = (AV_RB16(&whip->buf[ptr + 2]) + 1) * 4; if (ptr + len > ret) break; - if (pt == 205) { /* PT=RTPFB */ - uint8_t fmt = (whip->buf[ptr] & 0x1f); - if (fmt == 1 && len >= 12) { /* FMT=1 */ + if (pt == 205 && fmt == 1 && len >= 12) { /* PT=RTPFB, FMT=1 */ + int i; /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */ int srtcp_len = len + 4 + 10; int ret = ff_srtp_decrypt(&whip->srtp_recv, whip->buf, &srtcp_len); @@ -1956,12 +1956,13 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) // packet is invalid or authentication failed break; } + for (i = 0 ; 14 + i <= len; i = i + 4) { /** * See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1 - * TODO: Handle multi NACKs in bundled packet. + * Handle multi NACKs in bundled packet. */ - uint16_t pid = AV_RB16(&whip->buf[ptr + 12]); - uint16_t blp = AV_RB16(&whip->buf[ptr + 14]); + uint16_t pid = AV_RB16(&whip->buf[ptr + 12 + i]); + uint16_t blp = AV_RB16(&whip->buf[ptr + 14 + i]); /* retransmit pid + any bit set in blp */ for (int bit = -1; bit < 16; bit++) { @@ -1974,11 +1975,11 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) send_rtx_packet(s, it->pkt, it->size); av_log(whip, AV_LOG_INFO, "WHIP: NACK packet found: size: %d, seq=%d, blp=%d\n", it->size, seq, blp); } else - av_log(whip, AV_LOG_INFO, "WHIP: NACK packet, seq=%d, blp=%d, not found\n", seq, blp); + av_log(whip, AV_LOG_INFO, "WHIP: NACK packet, seq=%d, blp=%d, not found, the latest packet seq: %d\n", seq, blp, whip->history[whip->hist_head-1].seq); + } } - } } - ptr += len; + break; } } From 7bff40bfc803da6281810768545cd4303dd3ce77 Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Sun, 15 Jun 2025 10:59:53 +0800 Subject: [PATCH 18/24] avformat/whip: reindent for adding handle multi nack support Signed-off-by: Jack Lau --- libavformat/whip.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index 9bb7a798ca9e9..7ba67df96f51e 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -1947,16 +1947,16 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) if (ptr + len > ret) break; if (pt == 205 && fmt == 1 && len >= 12) { /* PT=RTPFB, FMT=1 */ - int i; - /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */ - int srtcp_len = len + 4 + 10; - int ret = ff_srtp_decrypt(&whip->srtp_recv, whip->buf, &srtcp_len); - if (ret < 0) { - av_log(whip, AV_LOG_ERROR, "WHIP: SRTCP decrypt failed: %d\n", ret); - // packet is invalid or authentication failed - break; - } - for (i = 0 ; 14 + i <= len; i = i + 4) { + int i; + /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */ + int srtcp_len = len + 4 + 10; + int ret = ff_srtp_decrypt(&whip->srtp_recv, whip->buf, &srtcp_len); + if (ret < 0) { + av_log(whip, AV_LOG_ERROR, "WHIP: SRTCP decrypt failed: %d\n", ret); + // packet is invalid or authentication failed + break; + } + for (i = 0 ; 14 + i <= len; i = i + 4) { /** * See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1 * Handle multi NACKs in bundled packet. @@ -1972,16 +1972,15 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) const RtpHistoryItem * it = rtp_history_find(whip, seq); if (it) { - send_rtx_packet(s, it->pkt, it->size); + // send_rtx_packet(s, it->pkt, it->size); av_log(whip, AV_LOG_INFO, "WHIP: NACK packet found: size: %d, seq=%d, blp=%d\n", it->size, seq, blp); } else av_log(whip, AV_LOG_INFO, "WHIP: NACK packet, seq=%d, blp=%d, not found, the latest packet seq: %d\n", seq, blp, whip->history[whip->hist_head-1].seq); } - } + } } break; } - } } else if (ret != AVERROR(EAGAIN)) { av_log(whip, AV_LOG_ERROR, "WHIP: Failed to read from UDP socket\n"); From fe8c500bbdb1a5d862b2b4831f03713c2ec4cfeb Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Sun, 15 Jun 2025 11:18:40 +0800 Subject: [PATCH 19/24] avformat/whip: simplify NACK verification logic Signed-off-by: Jack Lau --- libavformat/whip.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index 7ba67df96f51e..d72157e075bdf 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -1935,28 +1935,23 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) */ if (media_is_rtcp(whip->buf, ret)) { int ptr = 0; - while (ptr + 4 <= ret) { - uint8_t pt = whip->buf[ptr + 1]; - uint8_t fmt = (whip->buf[ptr] & 0x1f); + uint8_t pt = whip->buf[ptr + 1]; + uint8_t fmt = (whip->buf[ptr] & 0x1f); + if (ptr + 4 <= ret && pt == 205 && fmt == 1 ) { /** * Refer to RFC 3550, Section 6.4.1. * The length of this RTCP packet in 32-bit words minus one, * including the header and any padding. */ int len = (AV_RB16(&whip->buf[ptr + 2]) + 1) * 4; - if (ptr + len > ret) break; - - if (pt == 205 && fmt == 1 && len >= 12) { /* PT=RTPFB, FMT=1 */ - int i; + if (ptr + len < ret && len >= 12) { + int i = 0; /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */ int srtcp_len = len + 4 + 10; int ret = ff_srtp_decrypt(&whip->srtp_recv, whip->buf, &srtcp_len); - if (ret < 0) { + if (ret < 0) av_log(whip, AV_LOG_ERROR, "WHIP: SRTCP decrypt failed: %d\n", ret); - // packet is invalid or authentication failed - break; - } - for (i = 0 ; 14 + i <= len; i = i + 4) { + while (12 + i < len && ret >= 0) { /** * See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1 * Handle multi NACKs in bundled packet. @@ -1977,9 +1972,9 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) } else av_log(whip, AV_LOG_INFO, "WHIP: NACK packet, seq=%d, blp=%d, not found, the latest packet seq: %d\n", seq, blp, whip->history[whip->hist_head-1].seq); } + i = i + 4; } } - break; } } } else if (ret != AVERROR(EAGAIN)) { From 7c1458140debfb5a53be3feaacec9c602716b5be Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Sun, 15 Jun 2025 13:23:51 +0800 Subject: [PATCH 20/24] avformat/whip: fix srtcp buffer crosses boundary whip->buf size is 4096 rather than srtcp_len, srtcp data just use part of its memory, handle it need more logic. So allocate a temporary pkt to carry the SRTCP data Signed-off-by: Jack Lau --- libavformat/whip.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index d72157e075bdf..ad34258f5242d 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -1948,7 +1948,9 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) int i = 0; /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */ int srtcp_len = len + 4 + 10; - int ret = ff_srtp_decrypt(&whip->srtp_recv, whip->buf, &srtcp_len); + uint8_t *pkt = av_malloc(srtcp_len); + memcpy(pkt, whip->buf, srtcp_len); + int ret = ff_srtp_decrypt(&whip->srtp_recv, pkt, &srtcp_len); if (ret < 0) av_log(whip, AV_LOG_ERROR, "WHIP: SRTCP decrypt failed: %d\n", ret); while (12 + i < len && ret >= 0) { @@ -1956,8 +1958,8 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) * See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1 * Handle multi NACKs in bundled packet. */ - uint16_t pid = AV_RB16(&whip->buf[ptr + 12 + i]); - uint16_t blp = AV_RB16(&whip->buf[ptr + 14 + i]); + uint16_t pid = AV_RB16(&pkt[ptr + 12 + i]); + uint16_t blp = AV_RB16(&pkt[ptr + 14 + i]); /* retransmit pid + any bit set in blp */ for (int bit = -1; bit < 16; bit++) { @@ -1967,13 +1969,16 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) const RtpHistoryItem * it = rtp_history_find(whip, seq); if (it) { - // send_rtx_packet(s, it->pkt, it->size); + send_rtx_packet(s, it->pkt, it->size); av_log(whip, AV_LOG_INFO, "WHIP: NACK packet found: size: %d, seq=%d, blp=%d\n", it->size, seq, blp); - } else - av_log(whip, AV_LOG_INFO, "WHIP: NACK packet, seq=%d, blp=%d, not found, the latest packet seq: %d\n", seq, blp, whip->history[whip->hist_head-1].seq); + } else { + av_log(whip, AV_LOG_INFO, "WHIP: NACK packet, seq=%d, blp=%d, not found, the latest packet seq: %d, rtx seq: %d\n", + seq, blp, whip->history[whip->hist_head-1].seq, whip->rtx_seq); + } } i = i + 4; } + av_free(pkt); } } } From 4bb3833d3b3abad3c67ebb4c358839b5fcf608f5 Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Wed, 18 Jun 2025 18:47:49 +0800 Subject: [PATCH 21/24] avformat/whip: optimize log content need to be changed AV_LOG_DEBUG before official commit Signed-off-by: Jack Lau --- libavformat/whip.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index ad34258f5242d..64511b33c3c2a 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -1969,10 +1969,13 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) const RtpHistoryItem * it = rtp_history_find(whip, seq); if (it) { - send_rtx_packet(s, it->pkt, it->size); - av_log(whip, AV_LOG_INFO, "WHIP: NACK packet found: size: %d, seq=%d, blp=%d\n", it->size, seq, blp); + ret = send_rtx_packet(s, it->pkt, it->size); + av_log(whip, AV_LOG_INFO, + "WHIP: NACK, packet found: size: %d, seq=%d, rtx size=%d, lateset stored packet seq:%d\n", + it->size, seq, ret, whip->history[whip->hist_head-1].seq); } else { - av_log(whip, AV_LOG_INFO, "WHIP: NACK packet, seq=%d, blp=%d, not found, the latest packet seq: %d, rtx seq: %d\n", + av_log(whip, AV_LOG_INFO, + "WHIP: NACK, packet not found, seq=%d, blp=%d, latest stored packet seq: %d, latest rtx seq: %d\n", seq, blp, whip->history[whip->hist_head-1].seq, whip->rtx_seq); } } From f4d6c7a338876b6b6775aa5b150dc88c1b7c2ba5 Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Sat, 21 Jun 2025 10:33:30 +0800 Subject: [PATCH 22/24] avformat/whip: restore HISTORY_SIZE_DEFAULT set Signed-off-by: Jack Lau --- libavformat/whip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index 64511b33c3c2a..1895385f43c47 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -154,7 +154,7 @@ /** * Retransmission / NACK support */ -#define HISTORY_SIZE_DEFAULT 4096 +#define HISTORY_SIZE_DEFAULT 512 /* Calculate the elapsed time from starttime to endtime in milliseconds. */ #define ELAPSED(starttime, endtime) ((int)(endtime - starttime) / 1000) From 6443f08ca7ecdd7ce52a87630eae38ae70195467 Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Sat, 21 Jun 2025 15:45:07 +0800 Subject: [PATCH 23/24] avformat/whip: change rtx log log mode to VERBOSE Signed-off-by: Jack Lau --- libavformat/whip.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index 1895385f43c47..c2262316492cf 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -1970,13 +1970,13 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) const RtpHistoryItem * it = rtp_history_find(whip, seq); if (it) { ret = send_rtx_packet(s, it->pkt, it->size); - av_log(whip, AV_LOG_INFO, + av_log(whip, AV_LOG_VERBOSE, "WHIP: NACK, packet found: size: %d, seq=%d, rtx size=%d, lateset stored packet seq:%d\n", it->size, seq, ret, whip->history[whip->hist_head-1].seq); } else { - av_log(whip, AV_LOG_INFO, - "WHIP: NACK, packet not found, seq=%d, blp=%d, latest stored packet seq: %d, latest rtx seq: %d\n", - seq, blp, whip->history[whip->hist_head-1].seq, whip->rtx_seq); + av_log(whip, AV_LOG_VERBOSE, + "WHIP: NACK, packet not found, seq=%d, latest stored packet seq: %d, latest rtx seq: %d\n", + seq, whip->history[whip->hist_head-1].seq, whip->rtx_seq); } } i = i + 4; From fecd3ddedfb169ed199548ab9f4b476b4306b27a Mon Sep 17 00:00:00 2001 From: Jack Lau Date: Sat, 21 Jun 2025 17:05:37 +0800 Subject: [PATCH 24/24] avformat/whip: improve logic and add comments Signed-off-by: Jack Lau --- libavformat/whip.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libavformat/whip.c b/libavformat/whip.c index c2262316492cf..1fcbbf98b300d 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -327,7 +327,7 @@ typedef struct WHIPContext { int history_size; RtpHistoryItem * history; /* ring buffer */ int hist_head; - int enable_nack_rtx; + int enable_nack_rtx; /* TODO: using whip_flags */ } WHIPContext; /** @@ -1943,17 +1943,17 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt) * The length of this RTCP packet in 32-bit words minus one, * including the header and any padding. */ - int len = (AV_RB16(&whip->buf[ptr + 2]) + 1) * 4; - if (ptr + len < ret && len >= 12) { + int rtcp_len = (AV_RB16(&whip->buf[ptr + 2]) + 1) * 4; + /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */ + int srtcp_len = rtcp_len + 4 + 10; + if (srtcp_len == ret && rtcp_len >= 12) { int i = 0; - /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */ - int srtcp_len = len + 4 + 10; uint8_t *pkt = av_malloc(srtcp_len); memcpy(pkt, whip->buf, srtcp_len); int ret = ff_srtp_decrypt(&whip->srtp_recv, pkt, &srtcp_len); if (ret < 0) av_log(whip, AV_LOG_ERROR, "WHIP: SRTCP decrypt failed: %d\n", ret); - while (12 + i < len && ret >= 0) { + while (12 + i < rtcp_len && ret == 0) { /** * See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1 * Handle multi NACKs in bundled packet.