Files
Sprinter-SDCC/tests/cblstream/cblstream.c
T
snark13 8458ec65f4 libc/cbl: две точки входа вместо underrun_mode — malloc больше не в резиденте
Линкер тянет .rel целиком, поэтому malloc/free, стоявшие в мёртвой ветке
CBL_UNDERRUN_SILENCE внутри cbl_open, приезжали в резидент КАЖДОМУ
приложению — включая те, что льют тишину сами и кучей не пользуются.

Разведено:
  cbl_open(freq, fmt, pump, fill)          — ничего не аллоцирует;
  cbl_open_silence(freq, fmt, pump, fill)  — аллоцирует буфер тишины;
  _cbl_open_raw(...)                       — общее тело.
Параметр underrun_mode из публичного API убран: режим задаёт выбор функции.

cbl_close больше не зовёт free — иначе malloc возвращался бы тем же путём.
Буфер тишины живёт до выхода из программы и переиспользуется; его размер
запоминается, иначе открытие 16-бит после 8-бит писало бы memset'ом мимо
выделенного куска.  _cbl_open_raw указатель на буфер не трогает вовсе —
иначе cbl_open после cbl_open_silence терял бы уже выделенную память.

В дереве режим SILENCE не использовал никто: все три вызова (cblwav,
cblstream, PoP) передавали CBL_UNDERRUN_APP.

Итог для PoP: _CODE 24329 -> 23716 (−613 Б), свободно в резиденте 129 ->
747 Б.  make size-check чистый (67 программ), звук в MAME проверен —
насос отработал 696 запросов, pop_snd_ok/want = 1/1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:53:08 +03:00

141 lines
5.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* cblstream — потоковое чтение с диска ОДНОВРЕМЕННО с воспроизведением
* через CBL. Расчитан на быстрый носитель (HDD) — на floppy
* захлёбывается (см. tests/cblwav, где поэтому весь клип грузится в
* RAM заранее). speech30.pcm — ~39.6 c речи, моно 8-бит unsigned,
* 21.875 кГц.
*
* ЕДИНСТВЕННЫЙ случай из трёх cbl-тестов, где приложению НУЖНО своё
* кольцо (2026-07-07): read() — ESTEX-вызов, а fill()-callback CBL
* зовётся ИЗ ISR, где ESTEX/BIOS под запретом (см. cbl.h). Поэтому
* диск читается ТОЛЬКО в main (в собственное кольцо ring[]), а
* fill_stream() лишь копирует уже готовые байты из ring в стейджинг и
* пропихивает cbl_push_otir() — сам с диском не работает.
*/
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <cbl.h>
#include <irq.h>
#define RING_SIZE 2048u
#define READ_CHUNK 1024u
static uint8_t ring[RING_SIZE];
static uint16_t r_rd, r_wr, r_count; /* r_count: main под IRQ_DISABLE, fill() уже в ISR */
static uint8_t staging[128]; /* блок 8-бит моно */
static uint16_t ring_in = 0;
static uint16_t ring_border = 0;
static uint16_t ring_free(void)
{
IRQ_DISABLE();
uint16_t c = r_count;
IRQ_ENABLE();
return (uint16_t)(RING_SIZE - c);
}
/* Вызывать ТОЛЬКО из main. */
static void ring_push(const void *src, uint16_t n)
{
const uint8_t *s = src;
while (n) {
uint16_t tail = (uint16_t)(RING_SIZE - r_wr);
uint16_t chunk = n < tail ? n : tail;
memcpy(ring + r_wr, s, chunk);
r_wr = (uint16_t)((r_wr + chunk) % RING_SIZE);
IRQ_DISABLE();
r_count += chunk;
IRQ_ENABLE();
s += chunk;
n = (uint16_t)(n - chunk);
}
}
/* Зовётся ИЗ ISR — только копия из уже готового ring, никакого ESTEX. */
static int fill_stream(uint16_t n)
{
if (r_count < n) return 0;
uint16_t tail = (uint16_t)(RING_SIZE - r_rd);
if (tail >= n) {
// memcpy(staging, ring + r_rd, n);
cbl_push_otir(ring + r_rd, n);
ring_in++;
} else {
memcpy(staging, ring + r_rd, tail);
memcpy(staging + tail, ring, (uint16_t)(n - tail));
cbl_push_otir(staging, n);
ring_border++;
}
r_rd = (uint16_t)((r_rd + n) % RING_SIZE);
r_count = (uint16_t)(r_count - n); /* уже внутри ISR — IRQ и так замаскированы */
return 1;
}
int main(void)
{
int fd = open("speech30.pcm", O_RDONLY);
if (fd < 0) {
printf("open(speech30.pcm) failed: errno=%d\n", errno);
return 1;
}
static uint8_t readbuf[READ_CHUNK];
uint32_t played = 0;
uint8_t eof = 0;
/* прогреть кольцо реальными данными ДО cbl_open()/puts() — иначе
* пауза тишины на старте (тот же приём, что в cbltest.c/cblwav.c) */
while (!eof && ring_free() >= sizeof(readbuf)) {
int got = read(fd, readbuf, sizeof(readbuf));
if (got <= 0) { eof = 1; break; }
ring_push(readbuf, (uint16_t)got);
played += (uint32_t)got;
}
if (cbl_open(CBL_FREQ_21K9, CBL_FMT_MONO8, CBL_PUMP_OTIR,
fill_stream) != 0) {
printf("cbl_open failed: errno=%d\n", errno);
close(fd);
return 1;
}
puts("streaming speech30.pcm from disk (21.875 kHz, mono 8-bit)...");
while (!eof) {
if (ring_free() >= sizeof(readbuf)) {
int got = read(fd, readbuf, sizeof(readbuf));
if (got <= 0) { eof = 1; break; }
ring_push(readbuf, (uint16_t)got);
played += (uint32_t)got;
} else {
__asm__("halt"); /* ждём, пока ISR подъест кольцо */
}
}
close(fd);
printf("streamed %lu bytes from disk\n", played);
/* Ждём "почти полного" опустошения, а не точного RING_SIZE: если
* общая длина потока не кратна 128, необслуженный хвост (<128 Б,
* тот же квирк, что и в старом кольце libc — см. cbl.h) навечно
* оставит r_count>0, и точное ожидание никогда бы не завершилось. */
while (ring_free() < RING_SIZE - 127u) __asm__("halt");
printf("requests: %u, underruns: %u (expect 0 during play)\n",
cbl_requests(), cbl_underruns());
printf("ring: in: %u, border: %u\n",
ring_in, ring_border);
cbl_close();
puts("press any key (keyboard must work)...");
getch();
puts("cblstream done.");
return 0;
}