8458ec65f4
Линкер тянет .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>
141 lines
5.0 KiB
C
141 lines
5.0 KiB
C
/*
|
||
* 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;
|
||
}
|