The %pL and %pR bars followed every drop of the level, and had nothing
like the peak marker of the built-in peak meter. With the new "hold"
bar option a bar falls back at the peak meter's release rate, and the
highest level is held for the peak hold time, both from the same
settings as the built-in meter (peak_meter_get_times()). Without the
option the bars are as before.
The held peak is a block ending at the held level, 1 pixel thick or as
many as the optional number after "hold" says (eg. "hold, 4"). On a bar
with a fill image the block is drawn from the same part of the image,
so a bar drawn as LED segments holds a whole segment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I5c6262ba02b7d2df6c5f80f15d4a1d4354ef8e26
The SID codec never finishes on its own, so test_codec would decode
forever and a directory test would stall on the first SID file. Count
the decoded samples and halt the codec after 120 seconds of audio. Also
set the track length to that limit so the benchmark results are correct
and show the elapsed time while decoding.
Fixes FS#13662
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I4e7e99bf41d00c5597aae9d48b837e28df5b75c0
The rows that calibrated the Clip+ model in utils/perfsim, timed on
AS3525v2 with the tick timer's count for a 2/3 microsecond clock:
- calls, returns and loads into pc, interlocks after a load and
after a multiply, multiplies by operand size
- a miss with work after it, on each word of its line, back to
back, and evicting a dirty line; a load or store to the line
still filling
- stores to lines not cached: one stream, a word a line, four
words at a time, two streams turn about
- copies between uncached buffers, within a memory and across
- fetch misses in code written where it runs
- loops of adds and of branches by how many lines they cover, and
a cached pointer chase likewise
- the TTA filter stage by stage and Tremor's window loop
On AS3525v2 the miss rows run in DRAM and again in the RAM inside
the SoC, where codecs are loaded.
Rows that turned out to say nothing, or that a later row replaced,
stay in the file under TEST_CYC_ARCHIVE, each with why.
TEST_CYC_QUICK runs only the newest rows.
With these rows the plugin is about 140 KB on ARMv5, so it is left
out where the plugin buffer is 128 KB or less: the Clip, the m200v4
and the c200v2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I4ec45da514d0abaa02e1e041fd1a0387490994ed
A test plugin that measures what ARM instructions cost on the running
device. Each row times sixteen copies of one instruction in a loop and
subtracts the empty loop, giving cycles per instruction. Where plugins
get IRAM, each row is run with code and data in each combination of
IRAM and DRAM.
It covers ALU, shifts, branches, the load and store forms (including
halfword and register-offset loads), load-use, and the multiplier at
narrow and full-width operands. ARMv5 adds the DSP multiplies, clz,
qadd, ldrd/strd and pld; ARMv6 adds the top-word and dual 16-bit
multiplies, umaal, ssat, rev, the extends, pkhbt, the SIMD adds and
multiply result latency. Cache misses are priced by a pointer chase
and by sequential streams over working sets either side of the cache.
Timing uses the SoC's microsecond counter on PP502x, PP5002, S5L870x,
S5L8720, TCC7801 and i.MX233, and the tick elsewhere. Results go to
the screen and to /test_cyc.txt.
Built only with test plugins enabled, on native ARM targets that run
ARM code (not Cortex-M).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I4a41f6daaa27fb817f411de89901a117c5c70f4d
When the text reached the bottom of the screen it started again at
the top, over what was there, so the old lines showed through the
new. Scroll up a line instead, as test_codec does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I024eda05bbcca6f1539893ea609c89f562436e2f
The tests always ran on HOME_DIR, so a card could not be tested, and
the internal storage could not be left alone.
Where there is more than one volume the plugin now asks which to
test when it starts: HOME_DIR as before, or any volume the root
directory lists. "Select disk" in its menu changes it. The test
directory, the test file and the log are all on the disk chosen,
where the log used to go to HOME_DIR, so that testing a card writes
nothing to the internal storage. The log names the disk.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ib243fdd32fa205e00bc2b39d776e0fc8a420a1f0
What the built-in recording screen shows and the skin tags did not:
- %RS size of the current recording, as "1.5MB"
- %RP seconds in the pre-record buffer, empty unless pre-recording
- %Rc clips counted by the peak meter
- %Rt trigger state by name; as a conditional off, ready, steady, go,
postrec, retrig, continue
- %Rw recording warnings in hex, empty while there are none
- %Ri recording source by name; as a conditional the same on every
target: mic, line in, digital, FM radio
- %Rg gain of the source in dB, the left channel's for line in and FM
The manual gets a Recording section covering these and the recording
tags that were there, none of which it described.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I889f95fd472c472c2589393a6c77eff4a4fd5c8f
- %Rf printed the sample rate with "%lu.%1lu", a field width rather
than a precision: 44100 Hz came out as "44.100", 22050 Hz as "22.50".
It is now in kHz without trailing zeros: "44.1", "22.05", "48".
- %Re switched on the format setting plus one, so its text named the
next format: "aiff" for WAV up to nothing for MP3. It now names the
format set, MP3 as "mp3" like the others.
- %Rb printed the index of the MP3 bitrate setting, "12" for 128 kbps.
It now prints the bitrate in kbps. Dead code from 2009 goes.
- %Rm was true for stereo: rec_channels is 0 for stereo. The classic
status bar has shown the mono icon for stereo recordings since 2009.
- %Rn counted the minutes on past 59, so %Rh:%Rn:%Rs showed 01:61:05
after an hour. It now counts within the hour, as %Rs does.
As conditionals %Rf, %Re and %Rb keep their values, so the classic
status bar's sample rate, format and bitrate icons are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Id9fb5c0b71fc0a44c5c7b3239a74397cb71090bf
The NAND's SYS volume holds the bootloader's firmware and is hidden
unless a build defines HAVE_RK27XX_NAND_SYS. Getting at it, e.g. to
replace BASE.RKW, needed a special build.
Add "NAND SYS on next USB" to the debug menu. While it is set, the
next USB connection shows SYS through the USER drive, as "NAND SYS",
and the switch clears when that connection ends. It is decided once
per connection, so setting it while connected waits for the next
one, and it is held in RAM only. Writes still need FTL_ALLOW_WRITE.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Idda3f1c5e00bea511618021397a6cb3bb0be7361
Plugging in USB on the recording screen closes recording, but the input
stays selected: the screen switches it back to playback only when it
ends, which is after USB mode, at the unplug. On a Samsung YP-CP3,
whose codec passes the microphone or FM radio through to the
headphones while recording, the input could be heard all through USB
mode. Switch to playback before entering it.
Tested on a YP-CP3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I194d5e120cc39685aa43cea4741e826a86ed2c65
The DSP gets its output samplerate from the codec thread when a
track is played. test_codec did not set it, so its runs with the
DSP used the rate of the last track played, or the default if
there was none: after "Playback frequency" was changed, they
resampled to the old rate until something had been played.
Set it from the mixer for each file, as the codec thread does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Checksum with DSP" and "Checksum folder with DSP" give the CRC32
of the DSP's 16-bit output for a file or a folder, as "Checksum"
does for the codec's output. That lets the DSP of a device, with
its settings, be checked against a reference for several files in
one run; "Write WAV with DSP" writes one file, /test.wav.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both decoders rejected extended sequential JPEGs (SOF1), which are
baseline files in all but name when they have 8-bit samples, and
16-bit quantization tables, which libjpeg writes for very low
quality settings unless told to force baseline.
Accept SOF1 with 8-bit samples, and read 16-bit table entries. The
core loader keeps its tables in 16 bits and scales them for the IDCT,
so it rejects entries over 8191; libjpeg's largest at quality 1 is
4950. Files using more than two Huffman tables are still rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I705a61402b4a0ab252995f8559b7ff2f68b05d72
A file's length and first cluster are only written to its directory
entry when it is closed, and the log was closed at the end of
plugin_start() alone. Leaving by USB or by power off goes through
exit() instead, so a whole run's log could be left as an empty file
with its data in clusters nothing pointed to.
Close the log from an atexit handler, close it when a run over a
folder ends, and close and reopen it after each track so that at
most one result is lost if the player dies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I613d0ad8c637d7f48a82eb09bc310c6fcc6d77f9
Entering the FM screen with no presets asks whether to scan for them.
The question and the scan clear the screen, after fms_fix_displays()
had shown the skin's backdrop, and nothing showed it again: the skin
redraws only its viewports, so the backdrop stayed missing everywhere
else - the header bar of CabbieV2's FM screen among it.
Leave the FM screen for the question and the scan and enter it again
after, as for the other screens shown from it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I7caadf3ac2a03f2825176084d8969ae6e59af471
The results were drawn right after backlight_on(), which only queues
a request to the backlight thread. lcd_update() does nothing while the
LCD is off, and the plugin then blocked waiting for a key without
updating again, so the display could keep showing the last progress
line. Refresh the display periodically while waiting for a key.
Also scroll the log up when the screen is full instead of wrapping
around to the top and overwriting old lines, which made the output
hard to read when testing a whole folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regression introduced in f87ff3a9b, which made it possible for audio
playback to request a freq under 44.1KHz, instead of treating 44.1 as
a floor (and upsampling)
However, there is a report of 22KHz files playing back distorted on an
imx233 target.
IMO a 44KHz floor is reasonable, but this bug is a symptom of something
deeper. Perhaps the mp3 codec isn't doing the right thing, or there's
an issue in the pcm mixer somewhere, or the imx233 codec doesn't properly
handle 22KHz? Further investigation is warranted.
Change-Id: I751ce05f8605de7f90d3eb7b3c98873487df438b
On colour targets the image viewer hands every file its own decoder
rejects to jpegp, including damaged ones, but jpegp barely checks its
input. Corrupt and truncated files crashed or hung it:
- At the end of the file GETC() kept returning stale bytes, so marker
searches and table reads never ended. Feed EOI markers (FF D9)
instead, which ends every loop, and stop calling read() there. This
state is reset in OPEN(): the overlay loader does not clear .bss.
- A file ending before any scan decoded as a blank image. Report it as
corrupt instead.
- Out of range header values were used as array indexes: Huffman and
conditioning table IDs, Huffman table sizes, sampling factors, scan
component counts and spectral selection. Reject them, and frames of
zero width or with no components.
- Invalid Huffman codes walked past the code length table, run lengths
wrote past coefficient 63, and huge coefficients indexed past the
IDCT clamp table. Bound all three.
- An odd DAC segment length never ended its loop.
- The coefficient buffer size could overflow an int.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I49465f38d283e159274d2f381029280cea42a8f1
Accomplish this by checksumming the english language input
and (1) including that in binary files and (2) checking the
value matches what was compiled into the firmware image
Not sure if this is the best approach but it works.
Change-Id: I8f79ad1b9d1cdf69e6a085d7b3dd1b5e078af04b
jpegp converted every image from YCbCr, so RGB JPEGs showed scrambled
colours. That affects progressive RGB files, and now also baseline RGB
files the jpeg decoder rejects and hands on to jpegp, such as RGB with
the R component sampled 2x2.
Record the JFIF and Adobe APP14 markers, decide the colour space with
the same rule as the other decoders, and skip the YUV conversion for
RGB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: If023eeb612b7f8a891d21fbe0070ab43c5a08f17
Both decoders treated every 3-component image as YCbCr, so RGB JPEGs
(as written by cjpeg -rgb, and by some Adobe software) came out with
wrong colours.
Decide the colour space as libjpeg does: a JFIF marker means YCbCr;
otherwise the transform flag of an Adobe APP14 marker decides (0 is
RGB); otherwise component IDs 'R', 'G', 'B' mean RGB.
Core loader: on colour targets R, G and B are stored in place in the
row buffer and the YUV conversion is skipped. Greyscale builds now
also decode G and B for RGB and combine them into luma per block,
which needs every component to be one block per MCU; other RGB
layouts are rejected there.
Plugin: RGB needs one block per MCU for every component, otherwise it
is rejected (colour targets fall back to jpegp). Colour builds convert
the R, G and B planes to YCbCr in place after decoding, so display and
greyscale view modes are unchanged; greyscale builds combine R, G and
B into luma per block as the core does.
Code size on the e200: core loader +351 bytes, plugin decoder +603
bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ib24a0b7690ca4c00b3ac01b2f511309efeac5975
This allows fonts, backdrops, and wps/fms/sbs to be checked.
Note that while settings _names_ are validated, the _values_
can not always be checked. Detectable settings errors
are flagged, but are not considered fatal.
Change-Id: I2a2b7ad94462e983345a1e692eccd6bd57e90eb9
Seven plugins have no layout or bitmaps for a 400x240 landscape
screen - the Samsung YP-CP3's, which no target building plugins has had
in that orientation. Give each the 320x240 one: the same height, and
centred in the 80 pixels more width wherever a 320x240 background has
to line up with it.
- bubbles, invadrox, rockblox: the 320x240 layout and background,
centred; the margins are cleared
- sudoku, jewels: the 320x240 bitmaps; their layouts already centre
themselves or use the width
- superdom: the 320x240 box size and board items - boxes as wide as
the screen allows make the board taller than it
- wormlet: the sizes of 320x240
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I223154da5da2250ba0f6068106ba9bec242a29ee
Five layouts define SCORENUM_Y as SCORE_Y + (...) unbracketed, so the
playfield update after each frame, PLAYFIELD_Y + 1 - SCORENUM_Y -
FONT_HEIGHT high, added that part rather than subtracting it: on a
240-line screen it ran 25 lines past the bottom. Most LCD drivers clip
it; the rk27xx one did not, and nothing in the playfield moved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Icecf7780d81595db9d6e42125fc421cc1cb40339
Damaged and truncated JPEGs could make the image viewer read or write
outside its buffers:
- process_markers() trusted segment lengths, and Huffman table symbol
counts, so a segment running past the end of the file was parsed
from whatever memory followed it. Check that each marker segment,
and each Huffman table in it, lies within the file.
- A file without a complete SOS header was decoded from a NULL entropy
data pointer, as load_image() checked only for DQT and SOF. Require
SOS as well.
- img_mem() computed the image size in an int, which overflows for a
large image (a 65535x65535 file came out as 0), so the decode wrote
far past the buffer. Compute it in 64 bits and saturate.
Found with the jpeg-conformance files of the imazen codec-corpus,
which include truncated files and files from fuzzing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I1e2a9a4346fd1c0e34b0813267c6317dbf704bc7
When all three components share 1x2 or 2x1 sampling there is no chroma
subsampling, but each interleaved MCU holds two blocks per component.
The core loader assumed one chroma block per MCU, so these files (the
folder.jpg in the original report) decoded to garbage and have been
rejected since chroma sampling is validated.
Lay out the MCU generically in fix_headers(): each component's H x V
blocks in turn, with a per-block position that places chroma blocks
with the same offsets as luma. The MCU size and decode buffer now come
from the luma sampling in colour builds too, and the chroma IDCT scale
from the luma:chroma sampling ratio, which is unchanged for 1x1
chroma. The unused subsample_x/y fields are removed.
All other layouts decode byte-identically to before at every scale.
The new layouts decode byte-identically to the same image encoded as
4:4:4. Code size drops by 108 bytes on the e200 and struct jpeg by
20 bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I63a894d6609e56f3553a902afd6278ce70e04637
Both JPEG decoders accepted several baseline layouts they cannot decode
and produced garbage without an error:
- chroma with sampling factors other than 1x1 (the MCU layout is chosen
from luma alone, so any other chroma layout desynchronises)
- files written as more than one scan, where the first scan does not
hold every component (it was decoded as if it were interleaved)
- scans whose components are not in frame order
- a height of 0 in SOF, to be defined later by a DNL marker
Reject these in process_markers(). The imageviewer then falls back to
the jpegp decoder on colour targets, which handles all of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ia3373f2b934eef6e3f354b4d064faf2d89868050
Both JPEG decoders ignored the Tq selector in the frame header and
always dequantized luma with table 0 and chroma with table 1. Files
with a single shared table multiplied chroma by an empty table, and
files with separate Cb and Cr tables used the wrong one for Cr.
imageviewer/jpeg: build one dequantization table per component (3
instead of 2, +256 bytes) from the table it selects. tab_membership is
no longer used and is removed.
Core loader: the raw tables are pre-scaled in place for the IDCT, and
luma and chroma can use different IDCT scales. fix_quant_tables() now
maps each component to a table slot, copying a table that luma and
chroma share at different scales to a slot no component uses (there are
4 slots and at most 3 components, so one is always free), and rewrites
quanttable_select to that slot. No extra memory.
Selectors above 3 are rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: If04c61fb0fef95da11d98d9918ea7225d8a440b0
Both JPEG decoders ignored the DC/AC table selectors in the SOS header
and always decoded luma with tables 0 and chroma with tables 1, the
layout libjpeg writes by default. Files where all components share
table 0, or where the slots are assigned differently, decoded to noise.
Look up each component's tables from its selectors instead. Baseline
JPEG only allows tables 0 and 1, which both decoders already hold, so
this needs no extra memory; selectors above 1 are rejected. In the core
loader tab_membership is no longer used and is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ie1ec407ddecf1256fef410b1256b97af412fb194
The bit reader refilled from the input buffer without checking its end.
The end-of-data check in the decode loops only runs once per MCU row, so
a stream that desynchronises (or is truncated) read past the end of the
file buffer for the rest of the row. Return zero bytes past the end
instead; the pointer still advances so the per-row check stops the
decode.
Found with AddressSanitizer on a JPEG whose chroma is sampled more
densely than its luma.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Iaada1bc3cbf62b18f10fb2377c12d4d5cb9524de
The debug menu tests CONFIG_NAND == NAND_RK27XX to include the FTL
scheme finder. Most targets do not define CONFIG_NAND at all, so every
native build but rk27xx's warned twice:
"CONFIG_NAND" is not defined, evaluates to 0 [-Wundef]
Test that it is defined first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I82a1e8fa2f09aac1c5f0d746fb6d475e389d12d6
Many rk27xx targets have NAND whose format nobody has examined. The
finder reads ID block 1 and the first page of the first 512 blocks and
says which FTL formatted them: Scheme A by its remap-log blocks,
Scheme B by its bad-block table and data headers, another Scheme B
generation by other 0xFxxx tags. The later ID block layout ('RK27' at
0x0a) records the FTL area's BCH strength at 0x1ed - 8 on the HM-601,
14 on the Archos Vision 28 - and the scan reads in that mode.
It is read-only, shown in the debug menu as "View FTL scheme", and
built for targets whose NAND is not storage - none yet.
Run on dumps of an HM-601 it reports Scheme B, a Samsung YP-CP3
Scheme A, and an Archos Vision 28 the other Scheme B generation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I0528f9d2a6996089b6488a77b43942f6fd1f2c16
The FM screen had no actions for the rk27xx generic keypad; give it those
its keymap already maps (menu, play, stop, exit) in radio.c.
The board's tuner is an RDA5807P. It keeps being driven as a TEA5767, in
the chip's compatible mode, as the original firmware does: tuning, seek
and the stereo indicator work so, and the RDA mode would bring nothing
here - this variant has no RDS. Say so next to CONFIG_TUNER.
The tuner's audio is on the codec's line input 1: only that line is
powered and mixed in while the radio plays (RK27XX_CODEC_FM_LINE 1). With
line 2 instead the radio is silent.
Tested on the rk27generic board: manual tuning, seek, stereo indicator and
audio.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I2135ee484705ff0fce6d88e73288d8ed2aec6c2d
The keymap had no recording screen context, so on the recording screen
no key produced ACTION_REC_PAUSE and recording could not be started; nor
did the FM screen have a record action.
- recording screen: User starts and pauses, held opens a new file;
left/right set the gain of the selected line; Menu opens the settings
- FM screen: User records the radio (FM_RECORD enabled for this keypad)
User is the Rec key on the Samsung YP-CP3, which shares this keypad.
Tested only in a YP-CP3 build, not yet on hardware; the YP-R0 itself was
not built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I655865d0dcf3e72da4e2d1cba24bc296919ec261
The YP-CP3 uses Wolfson WM8750. Headphones are wired on OUT2 and
a headphone amplifier enabled by GPIO F2, active high -
all RE from the original firmware.
The original firmware runs the codec as I2S master in its 12 MHz "USB
mode", fed a fixed 12 MHz MCLK, which puts 44.1 kHz at 44.118. Rockbox
instead makes the rk27xx the master and clocks the codec from the codec
PLL at exactly 256 fs (CODEC_SLAVE, as every other rk27xx target with an
external codec), so the codec's CLOCKING register is its normal-mode
256 fs setting at every rate. 96 kHz is left out: the WM8750 cannot take
it at 256 fs.
- config: HAVE_WM8750, CODEC_SLAVE, rates 8-48 kHz; the WM8750 has
hardware tone controls, so HAVE_SW_TONE_CONTROLS goes
- ypcp3/wmcodec-ypcp3.c: register writes over the rk27xx I2C driver
- wm8751.c: on the YP-CP3, power on and drive OUT2 instead of OUT1, set
the volume there, and switch the amplifier with the outputs
- english.lang: the YP-CP3 gets the bass/treble cutoff settings the
WM8750 brings
Tested on a YP-CP3: playback at 44.1 and 48 kHz on headphones, pitch and
volume correct.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I870038fb6a21a9c26b025e3df7c220fd925f49f6
A single-component (grayscale) scan is non-interleaved, so its MCU is
one 8x8 block regardless of the sampling factors in the frame header
(T.81 A.2.2). Some encoders write H=2,V=2 for the lone component, which
sent both the imageviewer plugin decoder and the core loader down the
4:2:0 path: 6 blocks per 16x16 MCU, the image treated as colour, and
the entropy data overrun.
Force 1x1 sampling for single-component frames when parsing SOF0 so
these images use the 4:4:4 layout with one block per MCU. Also add the
missing else in fix_headers() in the core loader, matching the plugin.
Tested on a Sansa e200 with both the plugin and the core loader, and
the plugin in the e200 simulator and built for PC and for ARM (qemu).
Fixes FS#13749.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Iac2f925aab8cd602930470dca5da7dfb44e15961
We already shut down the playback path and the voice path, but that
doesn't necessarily mean the PCM hardware is idle.
Add a call to pcm_play_stop() to ensure the PCM sink is completely idle.
This will prevent a badly-timed callback from firing during a ROLO
operation.
Change-Id: I1eb8c105895fb47bc3d0af91b4d87345f5399aa4