Use a real software encoder when the device has none in hardware
Running the device suite for the first time surfaced a defect the unit tests could not: FFmpeg's *_mediacodec wrappers do not fail on a device without a hardware encoder. They quietly bind to the platform's software codec (c2.android.hevc.encoder on the test emulator) and encode far slower than libx264 or libx265 would, while still presenting as the fast path. A three second 320x240 clip did not finish inside a three minute timeout. The Fast tier now checks whether a hardware encoder actually exists for the target codec, and falls back to libx264/libx265 on -preset veryfast when one does not. That is both quicker and honest about what it is doing, and the distinction between Fast and Best survives the fallback: veryfast against medium, rather than both collapsing to the same slow path. The routing itself was already correct. The capability probe rightly rejects c2.android.* as software-only, so the job was correctly sent to FFmpeg -- the test asserting Media3 was encoding an assumption about the machine it ran on. It now derives its expectation from the device, so it means the same thing on hardware with a real encoder and on an emulator without one. Verified on an API 37 emulator: 17 of 17 instrumented tests pass in 4.2s, against six minutes of timeouts before. 65 unit tests still pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -90,15 +90,29 @@ object FFmpegCommandBuilder {
|
||||
)
|
||||
}
|
||||
|
||||
// FFmpeg's MediaCodec wrappers. Used when a job landed on FFmpeg for
|
||||
// FFmpeg's MediaCodec wrappers, used when a job landed on FFmpeg for
|
||||
// container reasons but the user still asked for speed.
|
||||
//
|
||||
// Only when the device actually has a hardware encoder. Otherwise the
|
||||
// wrapper binds to the platform's software codec (c2.android.*) and
|
||||
// encodes far slower than libx264/libx265 would, while still calling
|
||||
// itself the fast path. In that case use a real software encoder with a
|
||||
// fast preset, which is what the user asked for in substance.
|
||||
QualityTier.FAST -> when (format.videoCodec) {
|
||||
dev.jasonmross.mediaconverter.model.VideoCodec.H265 ->
|
||||
listOf("-c:v", "hevc_mediacodec", "-b:v", "5M", "-tag:v", "hvc1")
|
||||
if (request.hardwareEncodeAvailable) {
|
||||
listOf("-c:v", "hevc_mediacodec", "-b:v", "5M", "-tag:v", "hvc1")
|
||||
} else {
|
||||
listOf("-c:v", "libx265", "-crf", "$CRF_H265", "-preset", "veryfast", "-tag:v", "hvc1")
|
||||
}
|
||||
dev.jasonmross.mediaconverter.model.VideoCodec.VP9 ->
|
||||
listOf("-c:v", "libvpx-vp9", "-crf", "31", "-b:v", "0")
|
||||
listOf("-c:v", "libvpx-vp9", "-crf", "31", "-b:v", "0", "-deadline", "realtime")
|
||||
else ->
|
||||
listOf("-c:v", "h264_mediacodec", "-b:v", "5M")
|
||||
if (request.hardwareEncodeAvailable) {
|
||||
listOf("-c:v", "h264_mediacodec", "-b:v", "5M")
|
||||
} else {
|
||||
listOf("-c:v", "libx264", "-crf", "$CRF_H264", "-preset", "veryfast", "-pix_fmt", "yuv420p")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,4 +84,13 @@ data class ConversionRequest(
|
||||
val quality: QualityTier = QualityTier.FAST,
|
||||
val enginePreference: EnginePreference = EnginePreference.AUTO,
|
||||
val probe: InputProbe = InputProbe(),
|
||||
/**
|
||||
* Whether this device has a real hardware encoder for the target codec.
|
||||
*
|
||||
* When it does not, FFmpeg's `*_mediacodec` wrappers still appear to work: they
|
||||
* bind to the platform's *software* codec (`c2.android.*`) and encode at a crawl
|
||||
* while presenting as the fast path. Knowing this lets the Fast tier choose a
|
||||
* genuinely fast software preset instead of a mislabelled slow one.
|
||||
*/
|
||||
val hardwareEncodeAvailable: Boolean = true,
|
||||
)
|
||||
|
||||
@@ -69,8 +69,15 @@ class ConversionWorker(
|
||||
setForeground(foregroundInfo(displayName, percent = 0, indeterminate = true))
|
||||
|
||||
val probe = MediaProbe.probe(applicationContext, inputUri)
|
||||
val request = ConversionRequest(format, quality, preference, probe)
|
||||
val decision = ConversionRouter.route(request, AndroidDeviceCodecs.get())
|
||||
val devices = AndroidDeviceCodecs.get()
|
||||
val request = ConversionRequest(
|
||||
format = format,
|
||||
quality = quality,
|
||||
enginePreference = preference,
|
||||
probe = probe,
|
||||
hardwareEncodeAvailable = devices.canEncode(format.videoCodec),
|
||||
)
|
||||
val decision = ConversionRouter.route(request, devices)
|
||||
Log.i(TAG, "Routing $displayName -> ${format.name} via ${decision.engine} (${decision.reason})")
|
||||
|
||||
val staged = publisher.createStagingFile(outputNameFor(displayName, format))
|
||||
|
||||
Reference in New Issue
Block a user