An offline Android analyzer that looks beyond the file extension.
Decode it. Analyze it. Verify it.
SpectroFlac is not on Google Play. Pick one of these:
Obtainium (recommended: updates itself from GitHub releases)
- Install Obtainium on your phone.
- In Obtainium tap Add App and paste:
https://github.com/LinuxKernel44/SpectroFlac - Tap Add, then Install. Obtainium will tell you when a new release is out.
Manual APK
- Download
SpectroFlac-<version>.apkfrom the latest release. - Open it and allow Install unknown apps for your browser or file manager when Android asks.
Releases are signed with the same key every time, so an update installed over either route keeps your history. If you still have v1.0.0, uninstall it first: it used an older key and Android refuses to update across keys.
SpectroFlac is an Android application designed to answer a deceptively simple question:
Is this FLAC actually lossless?
A .flac extension alone doesn't tell you where the audio came from.
A file can be:
- a genuine lossless master;
- an MP3/AAC/Vorbis transcode wrapped in FLAC;
- a fake 96/192 kHz file containing only 44.1/48 kHz content;
- a 24-bit container containing only 16-bit samples;
- corrupted;
- or even an MP3 renamed to
.flac.
SpectroFlac decodes the audio on-device, analyzes the complete spectrum and reports the evidence behind its verdict.
GENUINE 96%
24 BIT 44.1KHZ 1616 KBPS FLAC
Instead of simply saying "lossless" or "lossy", SpectroFlac provides the measurements used to reach its conclusion.
- 🔬 Full-spectrum analysis
- 🎵 Lossy transcode detection
- 📈 Fake hi-res detection
- 🧬 Bit-depth analysis
- 🔐 FLAC MD5 verification
- 🧮 Frame CRC-16 verification
- 📊 RMS / peak / crest factor
- 📉 Clipping detection
- 🎚️ TT-style DR estimation
- 🧾 Encoder fingerprints (MP3 CBR/VBR, AAC, Vorbis, Opus)
- 🔎 Interactive spectrogram — pinch-zoom, pan, frequency/time cursor
- 🎛️ Per-channel spectrum (left, right, mid, side)
- 🎧 Stereo analysis — correlation, balance, width, phase problems
- 🧩 Joint-stereo artifact detection
- 🏷️ Metadata and tags
- 🖼️ Embedded cover art
- 📁 Recursive folder scanning
- 🧵 Parallel analysis — several files at once (Auto, or 1–8), with heat and battery protection
- 📋 Scan queue — per-file and overall progress, time left, pause / cancel / retry / reorder
- 🔔 Background scans with a progress notification
- 🎚️ Thread presets — Quiet, Balanced, Fast in one tap
- ✅ Finish notification — "118 genuine, 4 not genuine, 1 damaged" when a scan ends
- 🖼️ Spectrogram export — a PNG at any resolution, up to hundreds of megapixels, re-rendered from the file
- ⚙️ Settings — performance, notifications, queue, history, auto-export, appearance
- 📤 CSV / JSON export
- 🕘 Local analysis history
- 📱 Android share-sheet integration
- 🪟 Liquid glass UI
- 🔒 100% offline
- 🚫 No network access
- 🚫 No dangerous permissions — only a notification for background scans
| Home | Transcode detected |
|---|---|
![]() |
![]() |
| Genuine file | History |
|---|---|
![]() |
![]() |
| Check | Method |
|---|---|
| Lossy transcode | Peak-hold FFT across the entire track and spectral cutoff analysis |
| Encoder profile | Measured cutoff compared against known lossy encoder characteristics |
| Fake hi-res | Detects content limited to the Nyquist range of lower sample rates |
| Padded bit depth | Detects 24-bit containers whose lower 8 bits are consistently zero |
| Invalid FLAC | Container sniffing identifies files based on their actual contents |
| Corruption | STREAMINFO MD5 and FLAC frame CRC-16 verification |
| Loudness | RMS and sample peak measurements |
| Dynamics | Crest factor and TT-style DR estimation |
| Clipping | Sample-level clipping and consecutive clipped runs |
A file claiming to be:
24-bit / 96 kHz
might actually contain:
16-bit / 44.1 kHz
SpectroFlac can identify evidence of both conditions rather than trusting the metadata.
flowchart TD
A[FLAC / Audio File] --> B[Container Sniffing]
B -->|Not FLAC| X[Identify Actual Format]
B -->|FLAC| C[Custom FLAC Decoder]
C --> D[PCM Samples]
D --> E[FFT / Spectrum]
D --> F[Bit Depth Analysis]
D --> G[Sample Rate Analysis]
D --> H[Loudness & Dynamics]
C --> I[STREAMINFO MD5]
C --> J[Frame CRC-16]
E --> K[Lossy Transcode Detection]
E --> L[Fake Hi-Res Detection]
F --> M[Padded Bit Depth Detection]
G --> L
E --> N[Encoder Fingerprint]
I --> O[Integrity Result]
J --> O
K --> P[Analysis]
L --> P
M --> P
N --> P
O --> P
H --> P
P --> Q[Confidence]
Q --> R[Final Verdict]
The important distinction is that SpectroFlac does not rely solely on metadata.
The audio itself is decoded and measured.
Lossy encoders commonly introduce a characteristic high-frequency cutoff.
SpectroFlac performs a peak-hold FFT over the complete track and examines the resulting spectrum.
For example, approximate encoder profiles can look like:
128 kbps ────────────────╮
╰──── cutoff
192 kbps ───────────────────────╮
╰── cutoff
320 kbps ───────────────────────────────╮
╰─ cutoff
The measured cutoff is then compared with known encoder characteristics.
This can provide strong evidence that a supposedly lossless file originated from a lossy source.
The fingerprint table was calibrated by round-tripping full-band stereo noise through libmp3lame (CBR and VBR), FFmpeg's AAC, libvorbis and libopus at 44.1 and 48 kHz and measuring where each cut landed (scripts/make-samples.sh regenerates that corpus). Many encoders share the same cutoff, so a cut suggests candidates; it never names one.
Tap the spectrogram strip on a result to open the full view:
- pinch to zoom — fingers spread horizontally zoom time, vertically zoom frequency, diagonally both;
- drag to pan, double-tap to zoom in on a spot or back out;
- tap to place a crosshair that reads out the exact frequency, time and level; the Cursor toggle turns one-finger drags into cursor moves;
- switch between Mid, Left, Right and Side (stereo files);
- the Cutoff toggle draws the measured cutoff across the picture.
The result screen also shows a spectrum chart (peak-hold and average, per channel, with the cutoff marked) and a stereo card with a left/right correlation timeline.
For stereo files SpectroFlac measures the left/right correlation, the balance, the side-to-mid width, and the share of the track that is out of phase; it also flags a silent channel and near-mono files.
Joint-stereo coders (MP3 joint stereo, AAC M/S, Vorbis coupling) spend their bits on the mid channel and starve the side channel. SpectroFlac looks for a side channel whose spectrum stops well below the mid channel's, and for a stereo image that narrows sharply from the mid range to the treble. This is reported as supporting evidence and never changes the verdict on its own.
SpectroFlac is an analysis tool, not a magic provenance detector.
A genuinely dull master and a lossy transcode can sometimes produce similar spectra, especially when the cutoff is gradual.
Likewise, a lossy source can sometimes be made harder to identify through:
- upsampling;
- filtering;
- resampling;
- additional processing;
- multiple generations of transcoding.
For this reason, SpectroFlac reports a confidence value and the measurements behind it instead of pretending that spectral analysis can prove the origin of every file.
Two limits worth knowing:
- Very high bitrates leave no cut. AAC 256 kbps and up, LAME V0 and Vorbis q6 and up show no brick wall on a full-band signal, so the spectral test cannot see them. A clean spectrum proves nothing about those.
- The thresholds are calibrated on a small corpus: synthetic noise through FFmpeg's encoders, one real lossless album, and lossy transcodes of three of its tracks. On that corpus every genuine file came out genuine and every transcode was caught, but other encoders (Apple, FDK, Fraunhofer) and other kinds of music (classical, hi-res, old masters with steep filters) can land elsewhere.
The spectral test is strong evidence, not proof.
SpectroFlac uses a custom liquid glass interface.
On Android 13+, panels use AGSL runtime shaders to refract the animated background behind them.
The effect includes:
- glass-like refraction;
- animated backgrounds;
- chromatic dispersion;
- translucent panels;
- dynamic lighting;
- shader-based distortion.
On Android 10–12, the application falls back to a frosted-glass presentation while keeping the same interface and functionality.
| Layer | Technology |
|---|---|
| Language | Kotlin |
| UI | Jetpack Compose |
| Graphics | AGSL Runtime Shaders |
| Database | Room |
| Audio decoder | Custom Kotlin FLAC decoder |
| Signal processing | FFT / spectral analysis |
| Serialization | JSON |
| Build system | Gradle + Kotlin DSL |
| File access | Storage Access Framework |
| Minimum Android | Android 10 / API 29 |
| Modern graphics | Android 13 / API 33+ |
Kotlin · Jetpack Compose · AGSL · Room · FFT · Custom FLAC Decoder
app/src/main/java/com/spectroflac/
│
├── flac/
│ ├── BitInput
│ ├── Container sniffing
│ ├── FLAC bitstream parsing
│ └── Full FLAC decoder
│
├── analysis/
│ ├── FFT
│ ├── Streaming measurements (per-channel spectra, stereo sums)
│ ├── Spectral analysis and encoder fingerprints
│ ├── Stereo / joint-stereo analysis
│ ├── Dynamics analysis
│ └── Verdict engine
│
├── data/
│ ├── Room database
│ └── JSON serialization
│
├── export/
│ ├── Text reports
│ ├── CSV
│ ├── JSON
│ └── Share intents
│
└── ui/
├── Compose screens (incl. the interactive spectrogram)
├── Charts (spectrum, correlation)
└── glass/
└── AGSL liquid glass system
SpectroFlac intentionally does not delegate FLAC decoding to MediaCodec.
The platform decoder may return 16-bit PCM on some devices.
That would make important parts of the analysis unreliable.
For example:
24-bit FLAC
│
▼
Platform decoder
│
▼
16-bit PCM
│
└── ❌ Original bit depth is already lost
SpectroFlac instead performs its own decoding:
24-bit FLAC
│
▼
Custom Kotlin decoder
│
▼
Original PCM samples
│
├──► Bit-depth analysis
├──► FFT
├──► Dynamics
└──► MD5 verification
This allows the application to inspect the decoded samples without depending on device-specific audio decoder behavior.
Select any file through the Android file picker.
SpectroFlac will determine whether the file is actually a FLAC stream.
An MP3 renamed to:
song.flac
will not be treated as a valid FLAC.
Select a directory and SpectroFlac recursively analyzes its .flac files; or pick several files at once, or share several files to the app from a file manager. A single file still opens straight on its result; two or more go through the scan queue.
Files are queued while the folder is still being listed, so analysis starts immediately. More files or folders can be added while a scan runs.
- an overall bar with
37 / 120 files, a percentage and the time left (computed from the bytes processed over the last seconds, so it already reflects how many files run in parallel; "Estimating…" until a few seconds of data exist); - one progress bar per running file, each with its own cancel button;
- the waiting files, with a menu to analyse one next or remove it;
- the finished files, filterable (genuine, suspicious, not genuine, damaged, errors) and sortable, with CSV / JSON export;
- Pause / Resume, Cancel all, Retry failed, Clear finished.
A finished file keeps only its verdict and numbers, so a scan of thousands of files stays light on memory; tapping one re-analyzes it in a couple of seconds to rebuild the spectrogram and charts.
A FLAC file decodes sequentially, so parallelism means several files analysed at the same time; one file always stays on one thread. Auto runs half of the phone's cores (4 on an 8-core phone), fewer when the phone gets hot or Battery Saver is on; a fixed number from 1 to 8 can be chosen instead. On a 4-core emulator the same 12 files took 7.9 s with 1 thread and 2.8 s with 4 (×2.8).
While a scan runs, a foreground service with one progress notification keeps it going with the screen off (and holds a partial wake lock). It stops by itself when the queue is empty. It can be turned off in Settings.
Export spectrogram image (on the result screen and in the full spectrogram view) renders the spectrogram of the file to a PNG. Unlike a screenshot, it re-analyses the file at the resolution you ask for: the number of rows picks the FFT length (one bin per row or a little more) and the number of columns picks how far apart the analysis windows sit, so a bigger image really shows more detail, up to what the audio holds.
- Size: presets (Screen 1920×1080, 4K, 8K), any exact width × height (256 px up to 32,768 rows and 1,000,000 columns), or Maximum — the finest analysis the file allows (16,384 rows ≈ 1.3 Hz each at 44.1 kHz, one column every ~256 samples), shrunk to fit your free storage. The dialog shows the detail per pixel (FFT size, Hz per row, ms per column), the final image size, the estimated PNG size and render time, and refuses sizes that do not fit.
- Channel: Mid, Left, Right or Side for stereo files.
- In the image: frequency and time axes with labels, a header (file, format, verdict, analysis details), the track title and artist with a small cover, the measured cutoff line, and a dBFS colour legend; each can be switched off.
- Where it goes: Save as… (you pick the place and name) or Share.
- How it can be so large: the audio is decoded once to a temporary file, the columns are computed on all cores into a second temporary file laid out so it can be read back row by row, and the PNG is streamed out while it is composed, so memory use stays small whatever the size (a 16,384 × 16,384 spectrogram — a 20,275 × 19,695 image — took 47 s on a 4-core emulator with the Java heap around 40 MB). Only free storage and time limit it.
- It keeps running if you leave the app (foreground service with a progress notification and a Cancel button).
| Section | Options |
|---|---|
| Performance | presets Quiet (¼ of the cores, low priority), Balanced (Auto) and Fast (all cores but one, full priority); files at once (Auto / 1–8), background scan, thermal protection, low-priority threads, keep the screen on, pause below a battery level |
| Notifications and battery | notify when a scan finishes (on by default), the status of the notification permission and of the battery-optimization exemption, each with an Allow button |
| Queue | skip files already analysed (unchanged file, same app version), offer to resume after a restart (default: start fresh) |
| Results and history | history size, delete analyses older than N days, auto-export a CSV / JSON into a chosen folder when a scan ends |
| Appearance | liquid glass effects, animated backdrop |
SpectroFlac registers itself for FLAC files.
You can therefore use:
Share → SpectroFlac
or:
Open with → SpectroFlac
directly from compatible file managers.
Every analysis can be stored locally using Room.
Previous reports can be:
- reopened;
- inspected;
- exported;
- compared manually.
No account or cloud service is required.
SpectroFlac is designed to work entirely offline.
┌─────────────────────────────┐
│ Your device │
│ │
│ FLAC ──► Decode ──► FFT │
│ │ │ │
│ ▼ ▼ │
│ MD5 Verdict │
│ │
└─────────────────────────────┘
│
X
No network
- ❌ Internet access
- ❌ User accounts
- ❌ Cloud processing
- ❌ Audio uploads
- ❌ Storage permissions
Files are accessed through Android's Storage Access Framework and analyzed locally.
For background scans and exports the app declares a foreground service (data sync), a wake lock, the notification permission (Android 13+) and the battery-optimization exemption request. The first time a scan starts, a short dialog explains why, then Android's own prompts follow: notifications, then "Let app always run in background?". If you refuse, nothing breaks (scans still run, without a visible notification, and Android may slow a long scan down with the screen off); the Settings screen shows the state of both and lets you grant them later. None of these give the app access to anything outside the files you pick.
Clone the repository:
git clone https://github.com/LinuxKernel44/SpectroFlac.git
cd SpectroFlacBuild the debug APK:
./gradlew :app:assembleDebugThe APK will be generated at:
app/build/outputs/apk/debug/
Run the unit tests:
./gradlew :app:testDebugUnitTestSigning material is never committed to the repository.
Create a keystore:
keytool -genkeypair -v \
-keystore spectroflac.jks \
-alias spectroflac \
-keyalg RSA \
-keysize 4096 \
-validity 10000Create keystore.properties in the project root:
storeFile=/absolute/path/to/spectroflac.jks
storePassword=YOUR_PASSWORD
keyAlias=spectroflac
keyPassword=YOUR_PASSWORDBoth files are excluded through .gitignore.
Build the release:
./gradlew :app:assembleReleaseWithout keystore.properties, the release build still works but produces an unsigned APK.
The repository includes an automated release script:
scripts/release.shThe script:
- Reads
versionNamefromapp/build.gradle.kts - Builds the release APK
- Verifies the APK signature
- Creates or updates the corresponding GitHub release
- Attaches the signed APK
Before releasing, update:
versionCode
versionName
The resulting GitHub release follows:
vX.Y.Z
The FLAC decoder is tested against the MD5 checksum stored inside the FLAC STREAMINFO block.
This provides a strong end-to-end validation of the decoded audio.
Detection thresholds are also tested against samples with known provenance.
Set a sample directory:
SPECTROFLAC_SAMPLES=/path/to/samples \
./gradlew :app:testDebugUnitTestThe test suite expects:
real_*.flac → genuine
fake_*.flac → non-genuine
limit_*.flac → no expectation (encodes the spectral test cannot see)
scripts/make-samples.sh /path/to/samples builds such a corpus with FFmpeg (real stereo, MP3/AAC/Vorbis/Opus round-trips at several bitrates, upsampled and padded files) and checks each stage with ffprobe.
Synthetic tests (stereo statistics, the fingerprint table, per-channel layers) need no samples and always run.
The queue engine (scheduling, parallelism limit, pause, cancel, reorder, retry, thermal hold, skip-known, statistics, time-left estimator) has plain JVM tests with controllable fake analysers, so no device is needed.
The spectrogram export engine is unit-tested on the JVM too: the streaming PNG writer (validated by an independent decoder written from the spec, checking every chunk CRC), the strip-major level store that transposes the data, the STFT columns (a tone lights the right row; no splatter at the ends of the file), the planner (sizes, estimates, Maximum under a storage budget), axis ticks, layout and file names.
On a device or emulator there are instrumented tests:
- the export dialog (presets, Maximum, free-space and size limits, channel and content choices);
- the export itself on real files, decoded with Android's own
BitmapFactory: exact size, header text, the red cutoff line, a lossy file's brick wall at the right row, each channel, a 33-megapixel image checked throughBitmapRegionDecoder, progress phases, cancellation leaving no temporary file, the foreground service, a failed export leaving nothing behind (-e heavy trueadds the 268-megapixel one); - the interactive spectrogram, driven with real touch gestures — horizontal, vertical and diagonal pinch, pan, double-tap, cursor;
- the queue and settings screens (what is shown, what each control reports);
- an end-to-end run of the real queue, analyser, settings and database over real FLAC files: parallelism limits, per-file progress, pause/resume, cancel, skip-known, queue saving, and a timing check that parallel is really faster (
scripts/e2e-queue.shprepares the files).
./gradlew :app:connectedDebugAndroidTestUsing FFmpeg:
ffmpeg \
-f lavfi \
-i "anoisesrc=r=44100:duration=20:color=pink" \
-ar 44100 \
-sample_fmt s16 \
-c:a flac \
real_16_44.flacCreate a lossy intermediate:
ffmpeg \
-i real_16_44.flac \
-c:a libmp3lame \
-b:a 128k \
t.mp3Convert it back to FLAC:
ffmpeg \
-i t.mp3 \
-ar 44100 \
-sample_fmt s16 \
-c:a flac \
fake_mp3_128.flacThis produces a FLAC container whose audio originated from a 128 kbps MP3.
┌──────────────────────────────────────┐
│ │
│ GENUINE │
│ 96% │
│ │
│ 24 BIT · 44.1 KHZ │
│ 1616 KBPS │
│ │
├──────────────────────────────────────┤
│ │
│ Spectrum ✓ No obvious cutoff │
│ Bit depth ✓ Genuine 24-bit │
│ Sample rate ✓ 44.1 kHz content │
│ STREAMINFO ✓ MD5 valid │
│ Frames ✓ CRC valid │
│ Clipping ✓ None detected │
│ │
└──────────────────────────────────────┘
The application exposes the measurements behind the verdict rather than hiding them behind a single boolean.
- Spectrogram export to PNG at any resolution (re-rendered from the file, streamed to disk)
- Thread presets: Quiet, Balanced, Fast
- Notify when a scan finishes (on by default)
- Notification permission and battery-optimization exemption, asked on the first scan and manageable in Settings
- Parallel analysis of several files
- Scan queue with per-file and overall progress, time left, pause / cancel / retry / reorder
- Background scans with a progress notification
- Settings (performance, queue, history, auto-export, appearance)
- Pick several files, share several files, add files while a scan runs
- Full interactive spectrogram (pinch, pan, cursor), per-channel spectrum, stereo and joint-stereo analysis, more encoder fingerprints
- Calibrate the fingerprints and thresholds on more real music (one album so far)
- Fingerprints for Apple AAC, FDK-AAC and Fraunhofer MP3
- Multichannel (5.1 / 7.1) stereo-style analysis
- Compare two files (original vs. suspect)
- Watch a folder and scan new files automatically
SpectroFlac cannot determine the complete historical provenance of an audio file with certainty.
Its verdict is based on measurable characteristics of the file and decoded audio.
A file that has been heavily processed, filtered, resampled, or transcoded multiple times may not retain obvious evidence of its source.
Use the confidence score and underlying measurements as evidence, not absolute proof.
LinuxKernel44
GitHub: https://github.com/LinuxKernel44
Licensed under the PolyForm Noncommercial License 1.0.0.
You may use, modify, and share SpectroFlac for any noncommercial purpose, provided that every copy keeps the license terms and the Required Notice: credit line from LICENSE. Commercial use requires a separate agreement with the author.
SpectroFlac
Decode it. Analyze it. Verify it.



