Android's Missing MP3 Encoder: The Problem and Four Workarounds

D

Durganshu Mishra

Guest

A platform gap most developers meet the hard way, the four ways around it, and what each one costs.​



Every phone you have ever owned plays MP3s. No public Android API will make you one.

That sentence sounds wrong the first time you read it, and it is the single most surprising thing I learned building audio features for an Android assistant. Android’s supported media formats table has two columns, encoder and decoder, and MP3 has a tick in exactly one of them. The platform will happily decode an MP3 at any bitrate you throw at it. It will not encode one, at any effort, through any API the SDK exposes..

If you are building anything that produces audio on Android, you will meet this. Here is what it means and what the options actually cost.

The gap, precisely​


Two separate limits stack up, and confusing them wastes an afternoon.

One: there is no MP3 encoder in the platform. MediaCodec exposes the codecs the device ships. Every device ships an MP3 decoder. None is required to ship an MP3 encoder, and in practice stock devices do not. Asking for one returns nothing, on a Pixel and on a mid-range handset alike.

Two: the platform muxer will not write it even if you had one. MediaMuxer accepts a short list of audio MIME types for an MP4 container: AAC (audio/mp4a-latm), AMR-NB (audio/3gpp), and AMR-WB (audio/amr-wb). It can also write Opus to Ogg and Opus or Vorbis to WebM, but there is no MP3 branch to take in any of them. So even a hypothetical MP3 elementary stream has nowhere to go through the standard export pipeline.

Together these mean that a Media3 Transformer pipeline, which is the modern, supported, well-maintained way to do audio and video work on Android, can never emit an MP3. Not with a flag, not with a different output extension, not by trying harder. The blocker is the encoder, and no choice of muxer routes around a codec that is not on the device.


A note on the muxer, because this part moves. Media3 has been changing its output layer: recent versions default to an in-app MP4 muxer rather than wrapping the framework one, and the library now ships dedicated WebM, AAC, Ogg and WAV muxers that you can inject through a custom Muxer.Factory. So "you always get MP4 no matter what you name the file" is true of the default configuration and less true than it was of Media3 in general. What has not changed, and will not until someone ships an encoder, is that none of those muxers has an MP3 to write.

That produces the worst possible bug, and it is worth sitting with. Name the output song.mp3, and the export succeeds. You get a file with an MP3 extension containing an MP4 container with an AAC stream. Some players open it. Some refuse. The user has no way to diagnose it, and neither does your support inbox.

MP3 decodes on every Android device. The encoder column is empty.



Why the gap exists​


The usual explanation is patents, and the usual explanation is out of date.


MP3 was covered by a patent pool administered by Technicolor (formerly Thomson) alongside Fraunhofer IIS, and shipping an encoder meant licensing fees per unit. That was a live constraint for most of Android’s history and a perfectly good reason for AOSP to ship decode only. The last of the patents in that programme expired on 16 April 2017, and the programme itself was terminated a week later, on 23 April 2017.


So why is there still no encoder? Because by 2017 nobody in the platform wanted one. AAC encodes better at the same bitrate, is available on every Android version, and is already accepted by the MP4 muxer. Opus is better still at low bitrates and has had a platform encoder since Android 10, though the platform will only mux it into Ogg or WebM. Adding an MP3 encoder in 2017 would have been adding a worse codec, at maintenance cost, to serve compatibility with hardware that mostly stopped mattering.

That reasoning is correct and it does not help you, because your user does not want a codec. They want the thing they have called an MP3 since 2003.

Source unknown; widely circulated online. Happy to credit or remove on request — get in touch.



The four ways out​


Option 1: give them AAC in an .m4a and say so.

Zero setup, works on every device, stream copy where possible so it is instant and lossless. "AAC delivers the same perceived quality in fewer bytes than MP3, and .m4a plays in every browser, every phone, every modern car head unit, iTunes, WhatsApp, and Telegram.

The cost is the word. Some fraction of users specifically want the four characters .mp3, sometimes for a genuine reason: a DJ controller, a 2009 car stereo, a piece of lab equipment, a submission portal with a hardcoded file filter. The correct move is to make the substitution loudly, not silently. Say the file is an .m4a, say it plays everywhere MP3 does, say it holds the same quality in a smaller file, and offer the real conversion if they still want it.

Option 2: bundle an encoder through the NDK.

LAME is the reference free MP3 encoder and it is excellent. Building it into the app means a native library per ABI, an NDK build in CI, a meaningful APK size increase, and ongoing responsibility for a C codebase processing untrusted input. There is also a licence question to resolve deliberately rather than accidentally, since LAME is LGPL and your app’s licence has to accommodate it.


It also got harder in 2025. FFmpegKit, which is how most Android apps embedded FFmpeg, was retired on 6 January 2025, with its prebuilt binaries removed from Maven and GitHub in February and April, breaking builds everywhere. The maintainer’s explanation is the interesting part: MPEG LA had told him the project was not subject to patent fees, but after Via LA acquired MPEG LA in 2023 he could not get that confirmed again, and legal review raised enough risk to end the project. Development continues as FFmpegKitNext, source-only, alongside community forks. MP3’s own patents expired in 2017; the codec bundle around it did not become simple.


For an app whose core product is audio conversion, this is the right answer. For an assistant with a hundred other tools, it is a large permanent cost for one file extension.

Option 3: shell out to ffmpeg.

Termux ships an ffmpeg package carrying libmp3lame and a long list of other encoders. If your app can already run commands in Termux, MP3 conversion is one command line away, it runs entirely on the phone, and you inherit the whole codec matrix for free: MP3, AAC, Ogg Vorbis, Opus, WAV, FLAC.

This is the route we took, and it is not free either. Three costs, all of which we had to handle in code rather than in documentation:


  • The UID boundary. Termux runs under its own app UID, in its own sandbox, with its own private storage. Your app’s private directory is invisible to it, and every byte you hand over crosses a FUSE boundary on shared storage, which is slow above roughly a hundred megabytes. We cap conversion inputs at 512 MB, lower than the 2 GB cap on the local editing path, precisely because of this.

One thing this route gets for free is distribution. FFmpeg built with --enable-gpl --enable-libmp3lame is GPL, so an app that ships that binary inherits the obligation. We do not ship it. Termux does, the user installs it, and we send a command line across a process boundary.


There is also a setup cliff, and it starts earlier than you would like. Termux is not on the Play Store, so it comes from F-Droid or GitHub.Then pkg install ffmpeg has to have been run, and termux-setup-storage has to have been granted, or ffmpeg sees an empty /sdcard and reports a missing file for a file that plainly exists. Each of those has a distinct failure signature, and each deserves a distinct message. "Conversion failed" is not a message, it is a shrug.

Option 4: convert in the cloud.

Fastest to build, and it means the audio leaves the device. For a lot of apps that is an acceptable trade and it should be stated. For a tool whose entire premise is that your files stay on your phone, it is not an option at all, which is how a codec limitation ends up being an architectural constraint.

Source: Created by the author



Who closes this gap, and who doesn’t​


Nothing above is exotic. It is the ordinary situation for anyone who writes an Android audio feature, and the way different apps resolve it is informative. Dedicated converter apps close it, by bundling. ConvertIt, Simple Audio Converter, Video Transcoder and FFShare all encode MP3 on your phone with no network involved, because they ship FFmpeg inside the APK. That is the right call when conversion is the product: the encoder is always there, no setup, no second app. It is also why those apps carry a native build, a GPL obligation, and — as of 2025 — the maintenance of an FFmpeg wrapper that no longer has a blessed upstream. Cloud assistants close it, by uploading. That is a real answer and for many products the right one, but it means the recording of your meeting, your voice note, or your kid’s recital is now somebody’s server-side object. It is also useless on a plane. On-device assistants mostly do not close it at all. An assistant restricted to the platform APIs can extract audio, trim it, mute it, and hand you an .m4a. If nobody on the team read the encoder column, it will hand you an .m4a named .mp3 and never say so. The third row is the one we were in, and getting out of it took a second execution environment, a UID boundary, path translation, shell hardening, and six distinct error paths — a lot of engineering for one file extension, on top of a hundred tools that have nothing to do with audio.

What we actually shipped​


We took options 1, 2’s reasoning, and 3, layered, with the choice visible to the user. Everything runs on the phone. No audio is uploaded at any stage, including the MP3 conversion, which is the whole reason option 4 was never on the table."

The default local editing tool writes MP4 containers only, .mp4 for video and .m4a for audio, and refuses any other output extension rather than writing a mislabelled file. When someone asks for an MP3 and means "the audio", they get the .m4a with a one-line explanation of the substitution.

When someone specifically wants an MP3, a separate tool runs the ffmpeg conversion through Termux, locally. It is a separate tool rather than another operation on the first one for a reason worth stealing: their availability differs. The editing tool works with no setup. The conversion tool needs Termux. Keeping them separate lets the conversion tool be hidden entirely when Termux is not installed, so the model is never offered a capability it cannot deliver. An agent handed an unavailable tool does not fail cleanly, it invents a success.

Going from a video to an MP3 is therefore two steps: extract the audio to .m4a, which is a stream copy and takes seconds, then convert that to .mp3, which is a real re-encode. ffmpeg would happily do both in one invocation. We refuse the direct path on purpose, so that the lossy step is a thing you asked for rather than a thing that happened to you, and the error carries the two-step instruction.

And the errors are the actual product. Termux missing, ffmpeg not installed, storage never granted, codec missing from this ffmpeg build, input not decodable, output directory not writable: six different situations, six different messages, each naming the next command to run.

The converter apps and Gotcha end in the same place. Only one of them is also a copilot.



What this deliberately cannot do​

  • No video transcoding through the converter. Audio targets only. Video trimming, muting and compressing go through the local editing path.
  • No MP3 without Termux. If Termux is absent there is no MP3, only the .m4a and an explanation. We do not pretend otherwise, and we do not silently rename.
  • No lossless round trip. Converting an existing lossy file to MP3 re-encodes it, and generation loss is real. Converting from a lossless source is fine; converting your fourth-generation WhatsApp forward is not going to be.
  • No bitrate magic. You can ask for 320k. Asking for 320k on a 64k source produces a larger file containing exactly as much music as before.

That list is the honest version of the pitch. If all you want is MP3 conversion, a dedicated converter app will do it with less setup than we ask of you — install it and go, no Termux. What Gotcha gives you is that capability inside a copilot that also reads your screen, sends your texts and manages your files, without any of it leaving the phone.

Extract, convert, report. All of it through Termux’s ffmpeg on the handset, with the size change stated plainly — up, in this case, because the source was never 192 kbps to begin with.



Why an assistant should tell you which path it took​


This is the part that generalizes past MP3.

Some media operations copy the existing streams. Trimming, extracting an audio track, and dropping a video’s audio touch no pixels and no samples, so they finish in seconds on a long file and lose nothing. Others run the whole file through a codec: compressing, changing speed, joining clips, and every format conversion. Those take minutes and lose a little quality each time.


Same file, same player, same apparent result. The difference only shows up on the fourth pass. Source: Created by the Author
The user cannot see the difference. The file plays either way. But repeated re-encoding degrades a file visibly over a few rounds, and someone who does not know which operations are lossy cannot make a sensible decision about the order to do them in. So every result says which path it took, and the assistant is instructed to pass that on rather than swallow it.


That is the same principle as the .m4a substitution. The platform imposes a constraint. The tool’s job is not to hide the constraint behind a plausible-looking file, it is to make the constraint legible and then do the best available thing.

Other assistants tell you how. Gotcha does it, on your phone, and tells you what it did.

Try it​


Gotcha runs on Android 11 and above, and it is free and open source under the GNU AGPLv3. Media conversion appears once Termux is installed with its ffmpeg package.


If you are building your own audio feature on Android, the conversion path and its error handling are a few hundred readable lines. Take what is useful.
 

Thread statistics

Created
Durganshu Mishra,
Replies
0
Views
6
Back
Top