libc: IM2 Phase 2b — звук CBL/COVOX через callback fill(), без кольца libc
CBL уже имеет аппаратный буфер 256 Б (2×128, двойная буферизация на стороне железа) — держать поверх него ещё одно кольцо в libc было бы лишней копией. cbl_open(freq, fmt, pump_mode, underrun_mode, fill) регистрирует callback, вызываемый из ISR за очередным блоком; он сам пропихивает данные приложения (откуда угодно) через cbl_push_otir()/ cbl_push_accel() — без промежуточного буфера. - два насоса: OTIR (порт 0x4F) и ACCEL (акселератор, спец-страница EMM 0xFD@0xC000); OTIR+16-бит запрещён (EINVAL) — по исходнику MAME порт данных физически не может собрать 16-бит сэмпл из пары байт; - форматы CBL_FMT_MONO8/16/STEREO8/16, частоты 7.8..109к; - CBL_UNDERRUN_APP (по умолчанию, недолив не наша забота) / CBL_UNDERRUN_SILENCE (буфер тишины malloc'ится только в этом режиме); - tests/cbltest: матрица 64 комбинации (2 насоса × 8 форматов × 4 частоты); tests/cblwav: banked-стрим речи с дискеты (физстраницы кэшированы заранее — mem_get_page нельзя звать из fill()/ISR); tests/cblstream: единственный случай с собственным кольцом уровня приложения (диск нельзя читать из fill()). Verified в MAME 2026-07-07 — все три теста работают. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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,
|
||||
CBL_UNDERRUN_APP, 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;
|
||||
}
|
||||
Reference in New Issue
Block a user