Documentation · User manual · Testing
Looking for -hwaccel mediacodec or -c:v h264_mediacodec? Those upstream
spellings are supported. See
Upstream FFmpeg MediaCodec syntax compatibility.
DawnShell does not pass a general-purpose GPU into Debian. Debian handles
containers and packet framing, while Android MediaCodec performs H.264/AVC
and HEVC codec work.
The app's file-backed 1080p test proves the Android hardware decoder only. The commands below separately exercise the Debian static client, inherited shared transport, private NDK worker, and FFmpeg adapters.
- Enable Hardware codec bridge at Direct Boot in the app and save it.
- Run Configure Debian 13 systemd + SSH again.
- In Debian, verify the installed tools and private worker.
command -v dawnshell-ffmpeg dawnshell-hwdecode dawnshell-hwencode dawnshell-hwtranscode
sudo dawnshell-codec health --format jsonThe health JSON must contain all of the following:
worker_state=ready
transport=inherited_memfd_eventfd
public_listener=false
software_fallback=false
health starts a new worker, queries it, and reaps it. Its PID is expected to
change between commands; there is no daemon to keep alive.
This video-only command hardware-decodes H.264/HEVC through an Android Surface
and hardware-encodes AVC. require prevents silent fallback.
sudo env DAWNSHELL_FFMPEG_BRIDGE=require \
dawnshell-ffmpeg -hide_banner -y \
-i input.mp4 -map 0:v:0 -an \
-c:v libx264 -b:v 4M output.mp4The successful log reports the selected Android codec names,
surface_zero_copy, frame counts, and EOS.
# Hardware-decode H.264/HEVC to packed I420.
sudo dawnshell-hwdecode input.mp4 output.i420
# Convert the input to I420 in FFmpeg, then hardware-encode AVC or HEVC.
sudo dawnshell-hwencode input.mp4 output.mp4 4000000 avc
sudo dawnshell-hwencode input.mp4 output.hevc 4000000 hevc
# Keep the first input audio stream without re-encoding it.
sudo dawnshell-hwencode input.mp4 output.mp4 4000000 avc copy
# Surface hardware decode followed by AVC hardware encode.
sudo dawnshell-hwtranscode input.mp4 output.mp4 4000000dawnshell-hwdecode should use .i420 or .yuv when testing decode alone. A
container output makes /usr/bin/ffmpeg software-encode the decoded frames.
dawnshell-hwtranscode produces video-only AVC.
The ordinary FFmpeg spelling is also supported. -c:a copy remuxes the first
input audio stream with the hardware-encoded video; if the input has no audio,
the optional mapping still succeeds:
sudo ffmpeg -y -i input.mp4 -c:a copy \
-c:v h264_mediacodec output.mp4The wrapper leaves FFmpeg's banner, stream mapping, and live
frame/fps/time/speed lines visible. The same stderr is retained for failure
diagnostics.
dawnshell-live-encode captures or decodes input with FFmpeg, incrementally
frames I420 video for the Android MediaCodec AVC encoder, and immediately
muxes the returned Annex-B stream. It does not buffer the complete input or
output in temporary files.
Preview the pipeline without opening hardware:
dawnshell-live-encode --print-plan \
--input /dev/video0 --input-format v4l2 --input-pixel-format mjpeg \
--size 1280x720 --fps 30 --bitrate 4000000 \
--output recording.mp4Record a USB UVC webcam to fragmented AVC MP4:
sudo dawnshell-live-encode \
--input /dev/video0 --input-format v4l2 --input-pixel-format mjpeg \
--size 1280x720 --fps 30 --bitrate 4000000 \
--output recording.mp4Publish a rolling HLS playlist:
sudo install -d -m 0755 /var/www/html/live
sudo dawnshell-live-encode \
--input /dev/video0 --input-format v4l2 --input-pixel-format mjpeg \
--size 1280x720 --fps 30 --bitrate 4000000 \
--output-mode hls --hls-time 2 --hls-list-size 6 \
--hls-delete-segments --output /var/www/html/live/index.m3u8Add --record /srv/video/webcam.mp4 to an HLS command to write fragmented MP4
and HLS simultaneously. A network HLS/RTSP source can be passed with
--input URL --input-format auto.
Input decode, pixel conversion, and scaling currently run in Debian FFmpeg; Android hardware performs AVC encoding. Audio is not included. The encoder uses a two-second keyframe interval, so a two-second HLS segment target is the most predictable setting. Ctrl-C closes the pipeline and finalizes output.
A USB camera additionally requires host UVC/V4L2 support, a visible
/dev/videoX, devices-cgroup permission, and SELinux permission. Inspect it
with:
ls -l /dev/video* 2>/dev/null
v4l2-ctl --list-formats-ext -d /dev/video0Print the route selected by dawnshell-ffmpeg without processing media:
/usr/local/libexec/dawnshell-codec-ffmpeg.pl plan-ffmpeg \
-hide_banner -y -i input.mp4 -map 0:v:0 -an \
-c:v libx264 -b:v 4M output.mp4Expected plan:
action=transcode input=input.mp4 output=output.mp4 codec=avc bitrate=4000000
Trace every shell command and expanded variable in the selected direct wrapper:
sudo bash -x /usr/local/bin/dawnshell-hwtranscode \
input.mp4 output.mp4 4000000 \
2>&1 | tee /tmp/dawnshell-hwtranscode.trace.log
sudo bash -x /usr/local/bin/dawnshell-hwdecode input.mp4 output.i420
sudo bash -x /usr/local/bin/dawnshell-hwencode input.mp4 output.mp4 4000000 avcThe following is the wrapper-free equivalent of
dawnshell-hwtranscode input.mp4 output.mp4 4000000 for H.264 MP4 input.
sudo -i
input=/absolute/path/input.mp4
output=/absolute/path/output.mp4
bit_rate=4000000
temporary="$(mktemp -d /run/dawnshell-raw-transcode.XXXXXX)"
trap 'rm -rf -- "$temporary"' EXIT HUP INT TERM
stream_info="$temporary/stream.txt"
input_packets="$temporary/input-packets.json"
annex_b="$temporary/input.h264"
raw_packets="$temporary/raw-packets.json"
framed_input="$temporary/framed-input.bin"
framed_output="$temporary/framed-output.bin"
encoded="$temporary/output.h264"
client_log="$temporary/client.log"
/usr/bin/ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,width,height,avg_frame_rate \
-of default=noprint_wrappers=1 "$input" > "$stream_info"
width="$(sed -n 's/^width=//p' "$stream_info" | head -n 1)"
height="$(sed -n 's/^height=//p' "$stream_info" | head -n 1)"
frame_rate="$(sed -n 's/^avg_frame_rate=//p' "$stream_info" | head -n 1)"
integer_rate="$(printf '%s\n' "$frame_rate" | mawk -F/ \
'NF == 2 { printf "%d\\n", int(($1 / $2) + 0.5) }')"
/usr/bin/ffprobe -v error -select_streams v:0 -show_packets \
-show_entries packet=pts_time,dts_time -of json \
"$input" > "$input_packets"
/usr/bin/ffmpeg -hide_banner -loglevel error -y -i "$input" \
-map 0:v:0 -an -c:v copy -bsf:v h264_mp4toannexb \
-f h264 "$annex_b"
/usr/bin/ffprobe -v error -f h264 -show_packets \
-show_entries packet=pos,size -of json \
"$annex_b" > "$raw_packets"
/usr/local/libexec/dawnshell-codec-ffmpeg.pl pack \
"$input_packets" "$raw_packets" "$annex_b" "$frame_rate" "$framed_input"
/usr/local/bin/dawnshell-codec transcode \
avc avc "$width" "$height" "$integer_rate" "$bit_rate" \
< "$framed_input" > "$framed_output" 2> "$client_log"
cat "$client_log" >&2
frames="$(/usr/local/libexec/dawnshell-codec-ffmpeg.pl unpack-annexb \
"$framed_output" "$encoded" --require-keyframe)"
/usr/local/libexec/dawnshell-codec-ffmpeg.pl validate-stats \
"$client_log" "$frames"
/usr/bin/ffmpeg -hide_banner -loglevel error -y \
-r "$frame_rate" -f h264 -i "$encoded" \
-map 0:v:0 -an -c:v copy "$output"For HEVC input, replace h264_mp4toannexb with hevc_mp4toannexb, both
-f h264 occurrences with -f hevc, and transcode avc avc with
transcode hevc avc.
/usr/local/bin/dawnshell-codec is the irreducible static Android ELF client.
It creates one memfd and two eventfd objects, forks, then executes
/usr/local/libexec/dawnshell-codec-worker with only those inherited
descriptors. The bionic worker calls NDK AMediaCodec, returns bounded records,
and exits with its parent. There is no listening endpoint and no descriptor is
sent to another process. Plain glibc FFmpeg cannot call MediaCodec directly.
The managed native/chroot execution path is root-only, so bridge commands need
sudo from a regular Debian account.
/usr/local/libexec/dawnshell-codec-ffmpeg.pl is the packet-framing adapter
that sits between FFmpeg and that client. It is written for perl-base, which
Debian marks Essential, so a minimal Debian installation runs the hardware
bridge without installing any additional interpreter.
sudo dawnshell-ffmpeg-integration enable
hash -r
command -v ffmpegThe expected path is /usr/local/bin/ffmpeg. The wrapper directly executes
/usr/bin/ffmpeg for fallback, so it does not recurse. Programs that execute
the absolute /usr/bin/ffmpeg path bypass the wrapper.
Configuring Debian already enables this by default, so re-running it is usually unnecessary. Undo the integration with:
sudo dawnshell-ffmpeg-integration disable
hash -r| Value | Behavior |
|---|---|
auto |
Hardware for supported commands; otherwise /usr/bin/ffmpeg |
require |
Fail unless a hardware plan can be produced |
off |
Always use /usr/bin/ffmpeg |
The automatic hardware route accepts one input and output, H.264 or HEVC input,
AVC aliases or h264_mediacodec, ByteBuffer HEVC output through
hevc_mediacodec, raw .i420/.yuv, even dimensions from 16 through 4096,
1–240 fps, and a bitrate from 1000 through 100000000.
Filters, CRF/preset options, multiple inputs or outputs, video stream copy,
unsupported codecs, and audio encoding/filtering fall back to plain FFmpeg or
fail in require mode. ByteBuffer encode can preserve the first optional audio
stream with -c:a copy:
sudo ffmpeg -y -i input.mp4 -c:a copy \
-c:v h264_mediacodec -b:v 4M final.mp4Surface transcode remains video-only. For an unsupported audio layout, mux it afterward:
sudo env DAWNSHELL_FFMPEG_BRIDGE=require dawnshell-ffmpeg \
-y -i input.mp4 -map 0:v:0 -an -c:v h264_mediacodec -b:v 4M video-only.mp4
/usr/bin/ffmpeg -y -i video-only.mp4 -i input.mp4 \
-map 0:v:0 -map 1:a? -c:v copy -c:a copy final.mp4sudo dawnshell-codec health --format jsonOpen Hardware video acceleration → View hardware codec report in the app.
If the file-backed test passes but FFmpeg fails, investigate the streaming
client/worker path rather than hardware codec availability. If health does not
report worker_state=ready, check that the option is enabled, configure Debian
again, and verify these files:
sudo test -x /usr/local/bin/dawnshell-codec
sudo test -x /usr/local/libexec/dawnshell-codec-worker
findmnt /system /apex /linkerconfig 2>/dev/null || true
sudo dawnshell-codec health --format jsonworker startup timed out usually means the bionic dynamic linker view is
missing or the worker exited before its ready handshake. hardware codec ... unavailable means the Android platform did not expose a permitted hardware
component. Connection refused belongs to obsolete socket-based builds and is
not an expected error in this architecture.
If startup prints libandroidicu.so not found, Debian still has an older static
client that did not expose Android's immutable ICU APEX to the private worker.
Update DawnShell, open the app, and run Configure Debian 13 systemd + SSH
again. The current client removes inherited linker-control variables and adds
only the ABI-matching /apex/com.android.i18n/lib[64] directory when it exists.
Confirm the replacement with sudo dawnshell-codec health --format json; a
working installation reports worker_state=ready.