Duy Pham Case study

Android app and SDK · Solo project · May 2026 to present

Subly In closed testing

Live, translated captions over the videos and audio playing on your phone. Speech recognition and translation run entirely on the device, so no audio ever leaves it.

Role
Everything: SDK, app, model selection, benchmarks
Platform
Android 12 and later, tuned on a Galaxy S10 and S24 Ultra
Stack
Kotlin, Compose, sherpa-onnx, LiteRT-LM, ML Kit
Languages
English, Chinese, Japanese, Spanish and Vietnamese, both ways

Demo recording coming soon

Subly is in closed testing on Google Play. A recording of the caption overlay running over a video will appear here.

First translation
2.2 s
after speech starts, down from 11.5 s, on fast Mandarin dialogue
Caption after speech
1.5 s
from the speaker stopping to the translated caption, down from 6.1 s
Speech models measured
20+
on a 2019 phone, to pick one recogniser per language
Audio sent off the phone
None
recognition and translation run locally

01

Live captions in five languages, without the cloud

A video in a language you do not speak, a lecture or a stream with no subtitles. Cloud services can transcribe and translate it, but that means sending everything you listen to off the phone.

Subly does it on the phone itself. It captures the audio another app is playing, transcribes it, translates it, and draws the captions in a floating overlay on top of that app.

The constraints

Everything on the device, with no audio leaving it. Captions that keep up with fast, real conversation, not just clean read speech. And it has to work on a 2019 phone with 8 GB of RAM, not only on this year's flagship.

02

An SDK, and an app built on it

The engine is a standalone Android SDK, subly-android-sdk, with a Flow-based API: a session reports its state, and captions arrive as a stream. The app is a thin consumer of it, with onboarding, language selection and the overlay. Developing them as a Gradle composite build means an SDK change shows up in the app immediately, without publishing.

  1. 01

    Capture

    Audio another app is playing, through MediaProjection.

  2. 02

    Speech gate

    Silero VAD drops non-speech audio before recognition, at about 1% of real time.

  3. 03

    Recognise

    One sherpa-onnx model per language, streaming or on speech segments.

  4. 04

    Assemble

    Partial words become sentences, with punctuation restored where the model has none.

  5. 05

    Translate

    ML Kit, or Gemma 4 on LiteRT-LM on phones with enough memory.

The caption pipeline. Every stage is an interface, so recognisers and translators can be swapped per language and per device.

Model files are downloaded on first use rather than bundled, pinned to exact upstream revisions and checked against SHA-256 hashes where they come from mirrors. A first run downloads only the recogniser for the source language.

03 / Hard part 1

One speech model per language, chosen by measurement

No single speech recogniser is good at all five languages on a phone. I benchmarked more than twenty candidates on the Galaxy S10, the low-end baseline, against FLEURS clips with human references, and picked one per language with a fixed rule:

  1. 1Lowest error among models that decode at least twice as fast as real time on the S10.
  2. 2Prefer a streaming model within about 2 points of the best offline one, because streaming shows words as they are spoken.
  3. 3Among near-equals, prefer the smaller download.
Language Model Error
English Kroko Zipformer 5.1%
Chinese Zipformer + punctuation 13.9%
Japanese ReazonSpeech k2 10.4%
Spanish Kroko Zipformer 2.2%
Vietnamese Zipformer 30M 9.7%
The chosen recognisers. Error is from the release app on the Galaxy S10 with the clip played in a video player and captured like a user would: word error for en, es and vi, character error for zh and ja.

The measurements ruled out some obvious choices:

  • The most accurate model was too slow. Qwen3-ASR led on several languages, but ran at 0.6 to 0.85 times real time on the S10 and is a 987 MB download.
  • A streaming model that cannot stream. Parakeet's streaming variant took 12.7 seconds to decode each second of audio on the S10.
  • Segmentation matters as much as accuracy. Offline models caption one speech segment at a time. With a short pause rule, every breath in Vietnamese became its own caption and was translated as a fragment; a 0.8 s pause made captions whole sentences and cut word error from 12.6% to 9.7%. ReazonSpeech showed the opposite: a 9.4 s segment decoded as a single word, so Japanese keeps short segments.

Dropping the two older general-purpose engines from the app also shrank the release APK from 110.8 MB to 95.4 MB. They stay in the SDK for other consumers.

04 / Hard part 2

Translation that fits the phone it runs on

ML Kit translates a line in about 100 ms with a 30 MB model per language pair, and it runs anywhere. A small on-device LLM translates noticeably better: it knew that "elements" on the periodic table are chemical elements, where ML Kit chose the word for "factors". But the LLM is a 2.6 GB download and needs a phone with plenty of memory.

So the translator depends on the device. On phones with 10.5 GB of RAM or more, Subly uses Gemma 4 E2B through LiteRT-LM; elsewhere it uses ML Kit. A handover translator prepares both at once and starts with whichever is ready first, then switches to the LLM once it has loaded. That turned out to matter for more than speed: ML Kit's model downloads sometimes hung for over 20 minutes with no error, and the handover kept captions working regardless.

Model chrF First token Sentence
Qwen3-4B (before) 48.5 2,210 ms 5,271 ms
Gemma 4 E2B chosen 50.0 380 ms 1,641 ms
Hy-MT2 1.8B 49.7 1,089 ms 3,802 ms
On-device translation candidates on the Galaxy S24 Ultra GPU, 15 FLEURS sentences in 8 directions. chrF is a translation quality score against human references; higher is better.

Gemma 4 E2B matched or beat the previous model in 6 of 8 directions and was 3.2 times faster per sentence. Turning on its multi-token prediction drafter made sentences another 19% faster at the same quality, well short of the "up to 2.2x" headline, which was measured on a newer phone.

05 / Hard part 3

From 11.5 seconds to 2.2

The first full pipeline was accurate but late. On fast Mandarin dialogue translated to Vietnamese, the first translated words appeared 11.5 seconds after a sentence started. A per-caption latency tracker showed three causes, in order of size:

  1. 01The translator was slow

    Lines arrived faster than the old model could translate them, so they queued. Moving to Gemma 4 E2B fixed most of it.

  2. 02Sentences were committed late

    Nobody in a fast dialogue pauses for 0.8 s, so the recogniser only finished a sentence at its 10 s cap. An early committer now takes a sentence from the live hypothesis once it is complete, followed by more speech, and unchanged for about 0.4 s.

  3. 03Nothing was translated while someone spoke

    A live preview now translates the sentence still being spoken whenever the translator is idle, and is cancelled the moment committed work needs it.

Release app on a Galaxy S24 Ultra, fast Mandarin dialogue to Vietnamese, medians. Before: Qwen3-4B, no early commit, no live preview. After: Gemma 4 E2B with both.

Measured, then kept off

Translating each clause as it arrives sounded like an obvious win. Measured twice, with two different models, it made captions later: draft translations queued in front of finished sentences. It stays in the SDK, switched off, with the numbers in its documentation.

06

Measuring on real phones

Every model choice and default in Subly comes from a measurement, and the numbers live next to the code that depends on them. A dedicated benchmark module, never shipped, runs on physical phones: a 2019 Galaxy S10 with 8 GB as the low-end baseline and a Galaxy S24 Ultra with 12 GB.

  • A real test set. FLEURS clips in all five languages with human references, plus a real recording of fast Mandarin dialogue, scored by word or character error.
  • Tested the way users run it. The final numbers come from the release app with a clip playing in a video player and captured through MediaProjection, not from feeding audio files straight into the engine.
  • Honest comparisons. The S24 throttles after about an hour of GPU work, so A/B runs alternate on and off back to back. A control run caught a speech-gate change that had quietly moved more than a third of caption starts, and it was fixed.

07

Private by design

Audio is held only in short-lived memory buffers while it is processed, and captions are cleared shortly after they stop updating. Nothing is written to storage and no audio or text is sent anywhere. Even the benchmark logs only print transcripts when verbose logging is switched on explicitly, because transcripts are the user's speech.

Android asks for consent each time capture starts and shows a notification for as long as it runs. The network is used to download models and to send diagnostics, which the privacy policy spells out.

08

Status and next steps

  • In closed testing on Google Play, captioning and translating between English, Chinese, Japanese, Spanish and Vietnamese.
  • Japanese and Vietnamese use offline recognisers, so their source text appears about 2.5 s into a sentence, against about 0.5 s for the streaming languages. That is the next latency gap to close.
  • Gemma 4 E2B has not yet been measured on 8 GB phones. If it fits, it could replace ML Kit on the S10 class of device too.
  • Recognisers are chosen per language today; choosing them per device class, as translation already is, would let 12 GB phones use the most accurate model.

Numbers measured in October 2026.