One evening I was sorting through the long audio files that had piled up for the healing-sound app I maintain, and I wanted a quick index of what was inside each one. Missing a detail would mean redoing the pass, so I set resolution to high without a second thought.
Then I looked at the returned usage and stopped. The input token count was identical to the low run. Not close — identical.
I assumed I had written the parameter wrong, so I swapped keys and models and went around a few more times. What was wrong was not my configuration. It was my assumption about what this value actually decides.
Resolution sets the budget, not the picture quality
media_resolution — in the Interactions API you attach it per input as resolution — decides the maximum number of tokens allocated to a single image or video frame. It does not sharpen your photo, and it does not make the model smarter.
Miss that and a reasonable-sounding rule like "never compromise quality, so set everything to high" quietly adds cost on inputs where the knob does nothing at all.
Resolution is a budget setting, not an image setting. Reading the table with that sentence in mind is what finally made the behaviour make sense to me.
Where the knob moves, and where it sits still
The media resolution documentation lists approximate token counts per input type for the Gemini 3 family.
| Setting | Image | Video (per frame) | Audio (per second) | |
|---|---|---|---|---|
unspecified (default) | 1120 | 70 | 25 | 560 |
low | 280 | 70 | 25 | 280 + native text |
medium | 560 | 70 | 25 | 560 + native text |
high | 1120 | 280 | 25 | 1120 + native text |
ultra_high | 2240 | — | — | — |
Laid out side by side, my own mistake is right there in the row. Audio is the same per second at every level. Raising or lowering the setting moves nothing.
Video is not straightforward either. low and medium are both 70 tokens per frame, and the docs say plainly that the two are treated identically to conserve context. Choosing medium for video means paying the same as low and receiving the same thing. The only level that buys you something is high, and only when you need to read small text inside the frames.
PDFs carry the opposite warning. Document understanding tends to saturate at medium, and moving to high rarely improves recognition on ordinary paperwork.
The official numbers do not line up in one place
Here is the part where I was simply confused, and I would rather record it than tidy it away. Within ai.google.dev, audio and video token math appears in two forms.
The table above puts audio at 25 tokens per second. The Interactions API token guide puts audio at 32 tokens per second and video at 263 tokens per second. I checked both on 19 September 2026. The second page is marked Beta, and the two also differ in granularity — per frame in one, per second in the other.
Rather than deciding which page is wrong, I found it faster to look at how my own model actually counts. That goes double when a number is going into a quote for a client.
Numbers like these are not for reading. They are for counting on your own key.
Recounting on your own key
Send the same file at each level and line up usage.total_input_tokens. Pin the output short and the check costs almost nothing.
import os
from google import genai
client = genai.Client(api_key=os.environ["YOUR_API_KEY_ENV"])
MODEL = "gemini-3.8-flash"
uploaded = client.files.upload(file="samples/room-tone-60s.wav")
def input_tokens(resolution: str) -> int | None:
"""Send one input at a single resolution level and return its input tokens."""
try:
interaction = client.interactions.create(
model=MODEL,
input=[
{"type": "text", "text": "Answer with one word."},
{
"type": "audio",
"uri": uploaded.uri,
"mime_type": uploaded.mime_type,
"resolution": resolution,
},
],
)
except Exception as err:
print(f"{resolution}: failed ({type(err).__name__}: {err})")
return None
return interaction.usage.total_input_tokens
counts = {level: input_tokens(level) for level in ("low", "medium", "high")}
for level, tokens in counts.items():
print(f"{level:>6}: {tokens}")
distinct = {t for t in counts.values() if t is not None}
print("the level matters" if len(distinct) > 1 else "the level does nothing here")If the three numbers match, there is nothing to tune for that input type. If they diverge, the next measurement is how far down you can go before answer quality breaks. Keep the file, the prompt and the model fixed across runs — change one more thing and you will be reading two effects at once.
On the separate question of estimates from count_tokens drifting away from billed tokens, I walked through the causes in five reasons count_tokens estimates drift from your bill. What we are reading here is the usage block on the actual response.
Mixing levels inside a single request
In Gemini 3, resolution attaches to each input rather than the whole request. A chart whose small figures must be read correctly and a photo that only supplies context can be treated differently in the same call.
chart = client.files.upload(file="reports/sales-chart.png")
photo = client.files.upload(file="reports/site-photo.jpg")
interaction = client.interactions.create(
model="gemini-3.8-flash",
input=[
{"type": "text", "text": "Reconcile the figures in the chart with the scene in the photo."},
# high only for the one image where a misread means redoing the work
{"type": "image", "uri": chart.uri,
"mime_type": chart.mime_type, "resolution": "high"},
# low for the image that merely supplies context
{"type": "image", "uri": photo.uri,
"mime_type": photo.mime_type, "resolution": "low"},
],
)
print(interaction.usage.total_input_tokens)The gap between high and low is four times per image. When one picture needs accuracy and you level everything up to match it, that factor of four rides along on every other picture too. Per-item resolution is a Gemini 3 feature, so write the code assuming older generations will ignore it.
There is one more thread worth keeping in view for images. The image understanding guide also states that an image with both dimensions at or below 384 pixels counts as 258 tokens, while larger images are tiled into 768×768 crops at 258 tokens each. So a dimension-driven rule and a level-driven ceiling sit on the same site. Whether downscaling before upload is worth the effort is answerable in one sitting: send the same picture at a few sizes and compare total_input_tokens.
How I allocate levels now
As an indie developer running these passes daily, the line I draw is this. Inputs where a misread forces a redo — charts, screenshots, anything with text in the frame — go to high. Images that only supply context sit at low. PDFs start at medium and move up only when recognition actually breaks. Audio keeps its default level, and I trim the duration instead.
Output-side spend is a separate axis. For holding down reasoning tokens, controlling thinking_budget to protect cost is the more concrete read. Keeping input and output pinned separately is what lets you find the cause quickly when a bill jumps.
Pick the one input type you send most often and compare total_input_tokens at low and high. A single line of your own numbers changes how you read the table.