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..ef67cd50c4cc8 --- /dev/null +++ b/.github/workflows/format-patch.sh @@ -0,0 +1,149 @@ +#!/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" +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 + +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" +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 -n -e "$COMMIT_MSG" +echo "===================================================================" +echo "" + +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 + +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." + +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..1a3aefe3ab092 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,702 @@ +name: "Test" + +on: + push: + pull_request: + +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 +# $(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: 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 + + 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" \ + 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 + + if [[ "$STREAM" != "livestream" ]]; then + echo "Stream not found: $STREAM" + 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: + 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 --prefix=$HOME/.release/openssl && 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 + 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 + 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 -i bbb-4mbps-baseline-opus.mp4 -c copy \ + -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 + + 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 + + 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 --prefix=$HOME/.release/openssl + 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 + 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 + 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 -i bbb-4mbps-baseline-opus.mp4 -c copy \ + -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 + + 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 + + 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 --prefix=$HOME/.release/openssl + 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 + 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 + 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 -i bbb-4mbps-baseline-opus.mp4 -c copy \ + -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 + + 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 + + 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 --prefix=$HOME/.release/openssl + 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 + 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 + - 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" \ + 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 + + 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 + + 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 --prefix=$HOME/.release/openssl + 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 + 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 + - 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" \ + 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 + + 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 + + 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 + - srs + - asan + - openssl-1-0-1k + - openssl-1-0-2 + - openssl-1-1-0h + - openssl-3-0 + - openssl-latest + - generate-patch + steps: + - run: echo 'All done' + runs-on: ubuntu-22.04 + diff --git a/.gitignore b/.gitignore index 59c89da5e03b5..9e56f800f8c67 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ *.ptx *.ptx.c *.ptx.gz +*.patch *_g \#* .\#* @@ -45,3 +46,4 @@ /libavcodec/vulkan/*.c /libavfilter/vulkan/*.c /.*/ +/fate-suite diff --git a/libavformat/whip.c b/libavformat/whip.c index 710f24fc5ab97..6a33966472510 100644 --- a/libavformat/whip.c +++ b/libavformat/whip.c @@ -193,9 +193,14 @@ enum WHIPState { WHIP_STATE_FAILED, }; +typedef enum WHIPFlags { + WHIP_FLAG_IGNORE_IPV6 = (1 << 0) // Ignore ipv6 candidate +} WHIPFlags; + typedef struct WHIPContext { AVClass *av_class; + uint32_t flags; // enum WHIPFlags /* The state of the RTC connection. */ enum WHIPState state; /* The callback return value for DTLS. */ @@ -871,6 +876,7 @@ static int parse_answer(AVFormatContext *s) if (ptr && av_stristr(ptr, "host")) { char protocol[17], host[129]; int priority, port; + struct in6_addr addr6; ret = sscanf(ptr, "%16s %d %128s %d typ host", protocol, &priority, host, &port); if (ret != 4) { av_log(whip, AV_LOG_ERROR, "WHIP: Failed %d to parse line %d %s from %s\n", @@ -879,6 +885,11 @@ static int parse_answer(AVFormatContext *s) goto end; } + if (whip->flags & WHIP_FLAG_IGNORE_IPV6 && inet_pton(AF_INET6, host, &addr6) == 1) { + av_log(whip, AV_LOG_DEBUG, "WHIP: Ignore ipv6 %s, line %d %s \n", host, i, line); + continue; + } + if (av_strcasecmp(protocol, "udp")) { av_log(whip, AV_LOG_ERROR, "WHIP: Protocol %s is not supported by RTC, choose udp, line %d %s of %s\n", protocol, i, line, whip->sdp_answer); @@ -1885,13 +1896,22 @@ static int whip_check_bitstream(AVFormatContext *s, AVStream *st, const AVPacket } #define OFFSET(x) offsetof(WHIPContext, x) -#define DEC AV_OPT_FLAG_DECODING_PARAM +#define ENC AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { - { "handshake_timeout", "Timeout in milliseconds for ICE and DTLS handshake.", OFFSET(handshake_timeout), AV_OPT_TYPE_INT, { .i64 = 5000 }, -1, INT_MAX, DEC }, - { "pkt_size", "The maximum size, in bytes, of RTP packets that send out", OFFSET(pkt_size), AV_OPT_TYPE_INT, { .i64 = 1200 }, -1, INT_MAX, DEC }, - { "authorization", "The optional Bearer token for WHIP Authorization", OFFSET(authorization), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, DEC }, - { "cert_file", "The optional certificate file path for DTLS", OFFSET(cert_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, DEC }, - { "key_file", "The optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, DEC }, + { "handshake_timeout", "Timeout in milliseconds for ICE and DTLS handshake.", OFFSET(handshake_timeout), + AV_OPT_TYPE_INT, { .i64 = 5000 }, -1, INT_MAX, ENC }, + { "pkt_size", "The maximum size, in bytes, of RTP packets that send out", OFFSET(pkt_size), + AV_OPT_TYPE_INT, { .i64 = 1200 }, -1, INT_MAX, ENC }, + { "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 }, + { "whip_flags", "Set flags affecting WHIP connection behavior", OFFSET(flags), + AV_OPT_TYPE_FLAGS, { .i64 = 0 }, 0, UINT_MAX, ENC, .unit = "flags" }, + { "ignore_ipv6", "The optional Ignore any IPv6 ICE candidate", 0, + AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_IGNORE_IPV6 }, 0, UINT_MAX, ENC, .unit = "flags" }, { NULL }, };