Skip to content

Comments

Add hardware codec support for rkmpp#6182

Merged
WithoutPants merged 3 commits intostashapp:developfrom
a15355447898a:develop
Nov 9, 2025
Merged

Add hardware codec support for rkmpp#6182
WithoutPants merged 3 commits intostashapp:developfrom
a15355447898a:develop

Conversation

@a15355447898a
Copy link
Contributor

Added hardware codec support via rkmpp, tested on the RK3588.

Tested using the following Docker build script and Docker Compose configuration.

docker file

# syntax=docker/dockerfile:1

ARG GO_VERSION=1.24.3

FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bookworm AS builder

ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    ca-certificates \
    curl \
    build-essential \
    git \
    gnupg \
    python3 \
    python3-pip \
    tzdata && \
  rm -rf /var/lib/apt/lists/*

RUN <<'EOF'
set -eux
arch="$(dpkg --print-architecture)"
case "$arch" in
  arm64) goarch=arm64 ;;
  amd64) goarch=amd64 ;;
  armhf) goarch=armv6l ;;
  *) echo "unsupported architecture: $arch"; exit 1 ;;
esac
EOF

RUN <<'EOF'
set -eux
mkdir -p /usr/share/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource.gpg
echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" > /etc/apt/sources.list.d/nodesource.list
apt-get update
apt-get install -y nodejs
corepack enable
corepack prepare [email protected] --activate
rm -rf /var/lib/apt/lists/*
EOF

ENV PATH="/usr/local/go/bin:${PATH}" \
    CGO_ENABLED=1

WORKDIR /src

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN cd ui/v2.5 && yarn install --frozen-lockfile

RUN cd ui/v2.5 && yarn run gqlgen

RUN cd ui/v2.5 && yarn build

RUN go run github.com/99designs/gqlgen generate

RUN <<'EOF'
set -eux
GOOS="${TARGETOS:-linux}"
GOARCH="${TARGETARCH}"
GOARM=""
if [ "${GOARCH}" = "arm" ]; then
  case "${TARGETVARIANT}" in
    v7) GOARM=7 ;;
    v6) GOARM=6 ;;
  esac
fi
CGO_ENABLED=1 GOOS="${GOOS}" GOARCH="${GOARCH}" GOARM="${GOARM}" \
  go build -trimpath -ldflags="-s -w" -o /out/stash ./cmd/stash
EOF

FROM ubuntu:24.04

ARG DEBIAN_FRONTEND="noninteractive"

ENV \
  HOME="/root" \
  TZ="Etc/UTC" \
  STASH_CONFIG_FILE="/root/.stash/config.yml" \
  PY_VENV="/pip-install/venv" \
  PATH="/pip-install/venv/bin:$PATH" \
  LIBVA_DRIVER_NAME="rkmpp" \
  LD_LIBRARY_PATH="/usr/lib/jellyfin-ffmpeg:/usr/local/lib"

RUN touch /var/mail/ubuntu && chown ubuntu /var/mail/ubuntu && userdel -r ubuntu

RUN apt-get update && apt-get install -y \
    apt-utils \
    locales && \
  rm -rf /var/lib/apt/lists/* && \
  sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
  locale-gen

ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8

RUN apt-get update && apt-get install -y --no-install-recommends --no-install-suggests \
    gnupg \
    ca-certificates \
    libvips-tools \
    python3 \
    python3-pip \
    python3-venv \
    tzdata \
    wget \
    curl \
    yq && \
  rm -rf /var/lib/apt/lists/*

# Jellyfin APT Repository
RUN set -eux; \
  install -m 0755 -d /usr/share/keyrings; \
  curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key \
    | gpg --dearmor -o /usr/share/keyrings/jellyfin.gpg; \
  . /etc/os-release; \
  echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/jellyfin.gpg] https://repo.jellyfin.org/${ID} ${VERSION_CODENAME} main" \
    > /etc/apt/sources.list.d/jellyfin.list

# Rockchip Multimedia PPA
RUN set -eux; \
  curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x0B2F0747E3BD546820A639B68065BE1FC67AABDE" \
    | gpg --dearmor -o /usr/share/keyrings/rkmm.gpg; \
  . /etc/os-release; \
  echo "deb [signed-by=/usr/share/keyrings/rkmm.gpg] http://ppa.launchpadcontent.net/liujianfeng1994/rockchip-multimedia/ubuntu ${VERSION_CODENAME} main" \
    > /etc/apt/sources.list.d/rockchip-multimedia.list

RUN set -eux; \
  apt-get update; \
  apt-get install -y --no-install-recommends --no-install-suggests \
    jellyfin-ffmpeg7 \
    libdrm2 \
    libdrm-dev \
    libopencl1 \
    ocl-icd-opencl-dev \
    librga2 \
    librockchip-mpp1 \
    libv4l-rkmpp; \
  rm -rf /var/lib/apt/lists/*

COPY --from=builder /out/stash /usr/bin/stash

RUN useradd -u 1000 -U -d /config -s /bin/false stash && \
  usermod -G users stash && \
  usermod -G video stash

RUN apt-get purge -qq wget gnupg curl apt-utils && \
  apt-get autoremove -qq && \
  apt-get clean -qq && \
  rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/* /var/log/*

ENV PATH="${PATH}:/usr/lib/jellyfin-ffmpeg"

COPY --chmod=755 entrypoint.sh /usr/local/bin/entrypoint.sh

EXPOSE 9999
ENTRYPOINT ["entrypoint.sh"]

entrypoint.sh

#!/bin/bash

# Path to the config file
config_file="/root/.stash/config.yml"

# Extract the plugins_path from the config file
plugins_path=$(grep -E '^plugins_path:' "$config_file" | sed 's/plugins_path:[ ]*//')

# Extract the scrapers_path from the config file
scrapers_path=$(grep -E '^scrapers_path:' "$config_file" | sed 's/scrapers_path:[ ]*//')

# Initialize an empty variable to store the contents
all_requirements=""

# Check if plugins_path was found
if [ -z "$plugins_path" ]; then
    echo "Warning: plugins_path not found in $config_file"
else
    echo "Parsing plugin dependencies"
    # Find all requirements.txt files in plugins_path and process them
    while IFS= read -r -d '' file; do
        # Append the contents of the file to the variable
        all_requirements+="$(cat "$file")"$'\n'
    done < <(find "$plugins_path" -type f -name "requirements.txt" -print0)
fi

# Check if scrapers_path was found
if [ -z "$scrapers_path" ]; then
    echo "Warning: scrapers_path not found in $config_file"
else
    echo "Parsing scraper dependencies"
    # Find all requirements.txt files in scrapers_path and process them
    while IFS= read -r -d '' file; do
        # Append the contents of the file to the variable
        all_requirements+="$(cat "$file")"$'\n'
    done < <(find "$scrapers_path" -type f -name "requirements.txt" -print0)
fi

# Check if any requirements were found
if [ -z "$all_requirements" ]; then
    echo "No requirements found in either path."
else
    # Define a temporary file for combined requirements
    temp_requirements_file=$(mktemp)

    # Write the combined requirements to the temporary file
    echo "$all_requirements" >"$temp_requirements_file"

    # Define the output file path
    output_file="/root/.stash/requirements.txt"

    # Ensure the output directory exists
    mkdir -p "$(dirname "$output_file")"

    # Create a virtual environment and activate it
    python3 -m venv ${PY_VENV}
    source ${PY_VENV}/bin/activate

    # Install pip-tools
    pip install pip-tools

    # Use pip-compile to resolve and deduplicate the requirements
    pip-compile "$temp_requirements_file" --output-file "$output_file"

    # Clean up the temporary file
    rm "$temp_requirements_file"

    echo "Deduplicated requirements have been saved to $output_file"
    echo '───────────────────────────────────────

Installing dependencies...

───────────────────────────────────────
    '

    # Install the dependencies from the output file
    pip install -r $output_file
fi

PUID=${PUID:-911}
PGID=${PGID:-911}
if [ -z "${1}" ]; then
    set -- "stash"
fi
echo '
───────────────────────────────────────

This is an unofficial docker image created by nerethos.'
echo '
───────────────────────────────────────'
echo '
To support stash development visit:
https://opencollective.com/stashapp

───────────────────────────────────────'
echo '
Changing to user provided UID & GID...
'

groupmod -o -g "$PGID" stash
usermod -o -u "$PUID" stash
echo '
───────────────────────────────────────
GID/UID
───────────────────────────────────────'
echo "
UID:${PUID}
GID:${PGID}"
echo '
───────────────────────────────────────'
echo '
Starting stash...

───────────────────────────────────────
'
exec "$@"

docker compose

services:  
  stash:  
    image: a15355447898a/stash-rk:latest  
    container_name: stash-rk  
    restart: always  
    privileged: true  
    ports:  
      - "9999:9999"  
    logging:  
      driver: "json-file"  
      options:  
        max-file: "10"  
        max-size: "2m"  
    environment:  
      - STASH_STASH=/video/  
      - STASH_GENERATED=/generated/  
      - STASH_METADATA=/metadata/  
      - STASH_CACHE=/cache/  
      - STASH_PORT=9999  
    devices:  
      - /dev/dri:/dev/dri           # GPU/Display Interface  
      - /dev/dma_heap:/dev/dma_heap # DMA memory heap  
      - /dev/mpp_service:/dev/mpp_service # MPP VPU service core  
      - /dev/rga:/dev/rga           # RGA 2D accelerator  
      - /dev/mali0:/dev/mali0       # Mali GPU device, for OpenCL (HDR tone mapping)  
    volumes:  
      - /etc/localtime:/etc/localtime:ro  
      ## This directory stores configuration files, scrapers, and plugins  
      - /docker-file/stash/config:/root/.stash  
      ## Points to your data collection, e.g., where you store videos  
      - /docker-file/stash/video:/video  
      ## Stash's metadata storage location  
      - /docker-file/stash/metadata:/metadata  
      ## Other cache content storage location  
      - /docker-file/stash/cache:/cache  
      ## Stores binary large objects (e.g., scene covers, images, etc.)  
      - /docker-file/stash/blobs:/blobs  
      ## Stores generated content (screenshots, previews, transcoded files, sprites, etc.)  
      - /docker-file/stash/generated:/generated

The docker image is built on the rk3588 board like this sudo docker build --platform linux/arm64 -t stash-rkmpp .

@a15355447898a

This comment was marked as outdated.

@DogmaDragon

This comment was marked as outdated.

Copy link
Contributor

@NodudeWasTaken NodudeWasTaken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reviewed some of the code.
On another note, is the Rockchip encoder any good?
Then i might consider getting a good Rockchip SBC

Comment on lines +335 to +337
case VideoCodecRK264:
// For Rockchip, no extra mapping here. If there is no scale filter,
// leave frames in DRM_PRIME for the encoder.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the Rockchip decoder change for the format to nv12 so we dont need to enforce it here for full hardware encoding?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — RKMPP decodes to DRM_PRIME frames with NV12 as sw_pix_fmt, so you don’t need to force format=nv12 in the full-HW path (only when falling back to system memory/non-full-HW).

Comment on lines 365 to 372
case VideoCodecRK264:
if !fullhw {
return VideoFilter(sargs)
}
// Rockchip fallback chain for maximum compatibility:
// RGA scale → system memory → upload → rkmpp encoder.
// This avoids hwmap(rkrga→rkmpp) failures (-38/-12) seen on some builds.
template = "scale_rkrga=$value:format=nv12,hwdownload,format=nv12,hwupload"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"seen on some builds".
So this bug was fixed? If so you should check which version of ffmpeg this was fixed on.
So we can use the more optimal scale_rkrga

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting this error on my machine, and I don't have enough machines to test what's wrong, so I'm going with the safest option.
Theoretically, this shouldn't be a problem, but I'm getting the error here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting this error on my machine, and I don't have enough machines to test what's wrong, so I'm going with the safest option. Theoretically, this shouldn't be a problem, but I'm getting the error here.

I fixed the problem, it was a problem with the device tree and kernel of my system.

Alright, we're good to go with zero-copy now. The updated code's been deployed.

Comment on lines 381 to 383
// BUG: scale_rkrga expects positive sizes.
isRockchip := codec == VideoCodecRK264
return VideoFilter(templateReplaceScale(sargs, template, match, vf, isIntel || isApple || isRockchip))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 393 to 394
VideoCodecRK264:
return 4096, 4096
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@a15355447898a
Copy link
Contributor Author

a15355447898a commented Oct 26, 2025

I have reviewed some of the code. On another note, is the Rockchip encoder any good? Then i might consider getting a good Rockchip SBC

Compared to intel n100 qsv, the encoding quality is almost the same, but the speed will be much faster!

In the case of 4K HEVC 8bit → 1080p H.264 8 Mbps

RK3588:77 fps / 1.28× ,rtime 23.34 s
N100(QSV):58 fps / 0.97× ,rtime 30.83 s


Regarding the issue you raised, it's been addressed.

Copy link
Collaborator

@WithoutPants WithoutPants left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the necessary hardware to test this, but the code looks ok.

@WithoutPants WithoutPants added the feature Pull requests that add a new feature label Nov 6, 2025
@WithoutPants WithoutPants added this to the Version 0.30.0 milestone Nov 6, 2025
@WithoutPants WithoutPants merged commit 289b698 into stashapp:develop Nov 9, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Pull requests that add a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants