Sound through ALSA, a 2D game, and a stack walk that cannot panic
SDL 3 has no OSS backend and SchOS has no ALSA kernel side, so sound now goes SDL -> alsa-lib -> alsa-plugins' OSS plugin -> /dev/dsp. That path found two bugs underneath it: - ac97: resetting the PCM-out box also clears the descriptor list address, and nothing set it again. Every stream after the first stop played a "list" at physical address 0 -- silence -- and ALSA resets before each stream. If the engine halts at the end of the queue, the run bit is set again. - /dev/dsp: SETFRAGMENT, GETFMTS, GETCAPS, SETTRIGGER, GETOPTR and GETODELAY for the plugin's ring-and-pointer model, and poll(), writable once a whole fragment fits (ALSA spun at 76% CPU otherwise). speaker-test now plays in real time at 0% CPU, verified in a recording of QEMU's AC'97. - /etc/asound.conf: the default device is a plug to the OSS device at 16-bit stereo 48 kHz, what every SchOS audio driver plays. Also: - getstackpcs: each frame link must go up the same stack by less than KSTACKSIZE. The chain ends at the syscall entry with the user's rbp, which lbreakouthd uses as a register (-1024); the walk dereferenced it under the syscall tracer and the kernel panicked inside acquire(). - fb: FBIOGET_VSCREENINFO reports nominal timings, as Linux's efifb does, instead of zeros, which Xorg's fbdev turned into a 0 Hz RandR mode. - procdump (Ctrl-P) shows where runnable threads are in user space. - World: lbreakouthd instead of lbreakout2 (its stk.c passes vsnprintf a size of 511 plus the prefix length, and Alpine's fortified headers trap on that), plus curl, alsa-lib and alsa-plugins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
393bf674f5
commit
c77841ce3d
11 changed files with 237 additions and 12 deletions
|
|
@ -798,6 +798,7 @@ char* shm_page(int id, int pg);
|
|||
uint64 memfd_size(int id);
|
||||
int memfd_truncate(int id, uint64 len);
|
||||
int memfd_seals(int id, int add);
|
||||
int dsp_writable(void);
|
||||
int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing);
|
||||
void shm_detach(int id);
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ int audio_configure(struct audio_format *f); // 0, or negative
|
|||
int audio_write(const void *pcm, int bytes); // bytes queued, or negative
|
||||
void audio_drain(void);
|
||||
void audio_stop(void);
|
||||
int dsp_writable(void); // poll(): room for a write
|
||||
|
||||
// --- what userspace uses ---------------------------------------------------
|
||||
//
|
||||
|
|
@ -92,6 +93,23 @@ void audio_stop(void);
|
|||
#define SNDCTL_DSP_SYNC 0x00005001 // wait for the queue to drain
|
||||
#define SNDCTL_DSP_GETOSPACE 0x8010500C // struct audio_buf_info out
|
||||
#define SNDCTL_DSP_RESET 0x00005000 // stop, discard
|
||||
// The rest of what a buffer-and-pointer client needs -- ALSA's OSS plugin, which
|
||||
// is how SDL and everything else that speaks ALSA reaches this device.
|
||||
#define SNDCTL_DSP_SETFRAGMENT 0xC004500A // int in/out: count<<16 | log2(size)
|
||||
#define SNDCTL_DSP_GETFMTS 0x8004500B // int out: AFMT_* mask
|
||||
#define SNDCTL_DSP_GETCAPS 0x8004500F // int out: DSP_CAP_*
|
||||
#define SNDCTL_DSP_SETTRIGGER 0x40045010 // int in: PCM_ENABLE_OUTPUT or 0
|
||||
#define SNDCTL_DSP_GETOPTR 0x800C5012 // struct count_info out
|
||||
#define SNDCTL_DSP_GETODELAY 0x80045017 // int out: bytes not yet heard
|
||||
|
||||
#define DSP_CAP_TRIGGER 0x00001000
|
||||
#define PCM_ENABLE_OUTPUT 0x00000002
|
||||
|
||||
struct count_info {
|
||||
int bytes; // played since the last reset
|
||||
int blocks; // fragments played since the last GETOPTR
|
||||
int ptr; // where playback is in a ring of fragsize * fragments
|
||||
};
|
||||
|
||||
#define AFMT_S16_LE 0x00000010
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,19 @@ getstackpcs(addr_t *rbp, addr_t pcs[])
|
|||
if(((addr_t)rbp & 7) != 0)
|
||||
break;
|
||||
pcs[i] = rbp[1];
|
||||
rbp = (addr_t*)rbp[0];
|
||||
// The next frame is the caller's, further up the same stack: higher, and
|
||||
// by less than a whole kernel stack. The chain ends at the system call or
|
||||
// interrupt entry, and the rbp saved there is the user's -- which an
|
||||
// optimised program uses as an ordinary register. lbreakouthd left -1024
|
||||
// in it, 0xfffffffffffffc00, which passed every test above and faulted
|
||||
// here under the syscall tracer, taking the kernel down from inside the
|
||||
// lock it was recording.
|
||||
addr_t *next = (addr_t*)rbp[0];
|
||||
if(next <= rbp || (addr_t)next - (addr_t)rbp >= KSTACKSIZE){
|
||||
i++; // keep the return address just read
|
||||
break;
|
||||
}
|
||||
rbp = next;
|
||||
}
|
||||
for(; i < 10; i++)
|
||||
pcs[i] = 0;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,16 @@ static int audio_lock_init;
|
|||
// to be told once and then have it stay told.
|
||||
static struct audio_format current_fmt = { 48000, 2, AUDIO_FMT_S16LE };
|
||||
|
||||
// The OSS view of the stream, for clients that track a play pointer in a ring
|
||||
// rather than just writing: bytes accepted by the driver since boot, where the
|
||||
// last reset left the count, the ring geometry SETFRAGMENT asked for, and the
|
||||
// played count GETOPTR last reported (for its fragment count).
|
||||
static uint64 queued_total;
|
||||
static uint64 reset_base;
|
||||
static uint64 last_optr;
|
||||
static int frag_size = 4096;
|
||||
static int frag_count = 16;
|
||||
|
||||
static void
|
||||
lock_once(void)
|
||||
{
|
||||
|
|
@ -114,6 +124,10 @@ audio_write(const void *pcm, int bytes)
|
|||
}
|
||||
|
||||
done += n;
|
||||
lock_once();
|
||||
acquire(&audio_lock);
|
||||
queued_total += (uint64)n;
|
||||
release(&audio_lock);
|
||||
}
|
||||
|
||||
if (o->start)
|
||||
|
|
@ -211,6 +225,45 @@ dspclose(struct inode *ip)
|
|||
audio_stop();
|
||||
}
|
||||
|
||||
// Bytes the speaker has played since boot: everything queued, less what is
|
||||
// still waiting. A reset discards the waiting part, which then counts as played
|
||||
// -- the count only goes forward, and GETOPTR measures from the reset.
|
||||
static uint64
|
||||
played_total(struct device *d, const struct audio_ops *o)
|
||||
{
|
||||
uint64 q, p;
|
||||
int pend = o->pending ? o->pending(d) : 0;
|
||||
|
||||
lock_once();
|
||||
acquire(&audio_lock);
|
||||
q = queued_total;
|
||||
release(&audio_lock);
|
||||
p = (pend > 0 && (uint64)pend <= q) ? q - (uint64)pend : q;
|
||||
return p;
|
||||
}
|
||||
|
||||
// Writable once a whole fragment fits, as OSS defines it. ALSA's plugin sets
|
||||
// the fragment to its period and waits in poll() for one to be free; reporting
|
||||
// writable at the first free byte had it wake, find less than a period of
|
||||
// room, and poll again -- 76% of a CPU to play a sine wave.
|
||||
int
|
||||
dsp_writable(void)
|
||||
{
|
||||
struct device *d = audio_get();
|
||||
const struct audio_ops *o = ops_of(d);
|
||||
int want;
|
||||
|
||||
if (!o || !o->space)
|
||||
return 0;
|
||||
lock_once();
|
||||
acquire(&audio_lock);
|
||||
want = frag_size;
|
||||
release(&audio_lock);
|
||||
if (want > 65536)
|
||||
want = 65536; // never more than a driver's ring can free at once
|
||||
return o->space(d) >= want;
|
||||
}
|
||||
|
||||
int
|
||||
dsp_ioctl(int request, uint64 argp)
|
||||
{
|
||||
|
|
@ -262,18 +315,78 @@ dsp_ioctl(int request, uint64 argp)
|
|||
audio_drain();
|
||||
return 0;
|
||||
|
||||
case SNDCTL_DSP_RESET:
|
||||
case SNDCTL_DSP_RESET: {
|
||||
audio_stop();
|
||||
uint64 p = played_total(d, o);
|
||||
acquire(&audio_lock);
|
||||
reset_base = last_optr = p;
|
||||
release(&audio_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The ring the client thinks in. The driver's own ring is whatever it is;
|
||||
// these only shape the numbers GETOPTR and GETOSPACE report, which is all a
|
||||
// client does with them. Fragments of 256 bytes to 64 KiB, 2 to 1024 of them.
|
||||
case SNDCTL_DSP_SETFRAGMENT: {
|
||||
if (copy_from_user(&val, argp, sizeof(val)) < 0)
|
||||
return -EFAULT;
|
||||
int shift = val & 0xffff, count = (val >> 16) & 0xffff;
|
||||
if (shift < 8) shift = 8;
|
||||
if (shift > 16) shift = 16;
|
||||
if (count < 2) count = 2;
|
||||
if (count > 1024 || count == 0x7fff) count = 1024;
|
||||
acquire(&audio_lock);
|
||||
frag_size = 1 << shift;
|
||||
frag_count = count;
|
||||
release(&audio_lock);
|
||||
val = (count << 16) | shift;
|
||||
return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
case SNDCTL_DSP_GETFMTS:
|
||||
val = AFMT_S16_LE;
|
||||
return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_DSP_GETCAPS:
|
||||
val = DSP_CAP_TRIGGER;
|
||||
return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0;
|
||||
|
||||
// Enabling output starts whatever is queued. Disabling is accepted and does
|
||||
// nothing: the stream stops by itself when the queue runs dry, and stopping
|
||||
// it here would throw away what is queued, which a pause must not.
|
||||
case SNDCTL_DSP_SETTRIGGER:
|
||||
if (copy_from_user(&val, argp, sizeof(val)) < 0)
|
||||
return -EFAULT;
|
||||
if ((val & PCM_ENABLE_OUTPUT) && o->start)
|
||||
o->start(d);
|
||||
return 0;
|
||||
|
||||
case SNDCTL_DSP_GETOPTR: {
|
||||
struct count_info ci;
|
||||
uint64 p = played_total(d, o);
|
||||
acquire(&audio_lock);
|
||||
uint64 since = p - reset_base;
|
||||
uint64 ring = (uint64)frag_size * (uint64)frag_count;
|
||||
ci.bytes = (int)since;
|
||||
ci.blocks = (int)((p - last_optr) / (uint64)frag_size);
|
||||
ci.ptr = (int)(since % ring);
|
||||
last_optr = p;
|
||||
release(&audio_lock);
|
||||
return copy_to_user(argp, &ci, sizeof(ci)) < 0 ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
case SNDCTL_DSP_GETODELAY:
|
||||
val = o->pending ? o->pending(d) : 0;
|
||||
return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0;
|
||||
|
||||
case SNDCTL_DSP_GETOSPACE: {
|
||||
struct audio_buf_info bi;
|
||||
int space = o->space ? o->space(d) : 0;
|
||||
|
||||
bi.fragsize = 4096;
|
||||
bi.fragsize = frag_size;
|
||||
bi.bytes = space;
|
||||
bi.fragments = space / bi.fragsize;
|
||||
bi.fragstotal = 0;
|
||||
bi.fragstotal = frag_count;
|
||||
if (copy_to_user(argp, &bi, sizeof(bi)) < 0)
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -147,6 +147,23 @@ fbclose(struct inode *ip)
|
|||
// the largest struct here -- fb_fix_screeninfo is half the size of
|
||||
// fb_var_screeninfo, and a buffer sitting near the end of a mapping is a
|
||||
// legitimate place to put the smaller one.
|
||||
// Timings for a mode the firmware set and never described. GOP and VBE give a
|
||||
// size and a pixel format and nothing about the signal, so these are made up --
|
||||
// and made up the way Linux's efifb makes them up, which is what every program
|
||||
// that reads them was written against. They used to be zero, which is not "no
|
||||
// answer" but the answer "0 Hz": Xorg's fbdev driver built a RandR mode with a
|
||||
// dot clock of 0 from it, and SDL 3 sized a fullscreen window from that mode
|
||||
// as 1x1. Physical size stays zero; nothing reads it as a number.
|
||||
static void
|
||||
nominal_timings(struct fb_var_screeninfo *v)
|
||||
{
|
||||
if (v->xres == 0 || v->yres == 0)
|
||||
return;
|
||||
v->pixclock = 10000000 / v->xres * 1000 / v->yres;
|
||||
v->left_margin = (v->xres / 8) & 0xf8;
|
||||
v->hsync_len = (v->xres / 8) & 0xf8;
|
||||
}
|
||||
|
||||
int
|
||||
fb_ioctl(int request, addr_t arg)
|
||||
{
|
||||
|
|
@ -169,8 +186,7 @@ fb_ioctl(int request, addr_t arg)
|
|||
v->bits_per_pixel = gfx_fb_bpp();
|
||||
v->activate = FB_ACTIVATE_NOW;
|
||||
pixel_format(v, v->bits_per_pixel);
|
||||
// height/width (physical millimetres) and every timing field stay zero:
|
||||
// VBE does not report them and a guess would be worse than an admission.
|
||||
nominal_timings(v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -212,6 +228,7 @@ fb_ioctl(int request, addr_t arg)
|
|||
v->bits_per_pixel = gfx_fb_bpp();
|
||||
v->activate = act;
|
||||
pixel_format(v, v->bits_per_pixel);
|
||||
nominal_timings(v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -234,6 +234,12 @@ ac97_queue(struct device *d, const void *pcm, int bytes)
|
|||
// just written is what makes this a queue rather than a one-shot.
|
||||
outb(a->nabm + PO_LVI, (uint8)idx);
|
||||
|
||||
// An engine that ran off the end of the queue halts. QEMU resumes it when
|
||||
// LVI moves; the datasheet asks for the run bit again, so if it is still
|
||||
// halted after the write above, give it that.
|
||||
if (a->running && (inw(a->nabm + PO_SR) & SR_DCH))
|
||||
outb(a->nabm + PO_CR, CR_RPBM | CR_IOCE);
|
||||
|
||||
release(&a->lock);
|
||||
return take;
|
||||
}
|
||||
|
|
@ -263,6 +269,13 @@ ac97_stop(struct device *d)
|
|||
acquire(&a->lock);
|
||||
outb(a->nabm + PO_CR, 0);
|
||||
outb(a->nabm + PO_CR, CR_RR); // reset the box: CIV, LVI, PICB to 0
|
||||
for (int spin = 0; spin < 100000 && (inb(a->nabm + PO_CR) & CR_RR); spin++)
|
||||
;
|
||||
// The reset clears the descriptor list's address too, and the engine then
|
||||
// walks a "list" at physical address 0: every write after the first stop
|
||||
// played zeros. ALSA resets the device before each stream, so that was all
|
||||
// of them. Linux's intel8x0 rewrites it after every reset for this reason.
|
||||
outl(a->nabm + PO_BDBAR, a->bdl_phys);
|
||||
for (int i = 0; i < NDESC; i++)
|
||||
a->filled[i] = 0;
|
||||
a->head = 0;
|
||||
|
|
|
|||
|
|
@ -427,6 +427,11 @@ file_poll(struct file *f, int events)
|
|||
(f->ip->major == PTM_MAJOR || f->ip->major == PTS_MAJOR))
|
||||
return pty_poll(f->ip, events, f->readable, f->writable);
|
||||
|
||||
// /dev/dsp is writable when the driver has room. ALSA's OSS plugin waits
|
||||
// in poll() for exactly this between periods.
|
||||
if(f->ip && f->ip->type == T_DEV && f->ip->major == AUDIO_MAJOR)
|
||||
return (events & POLLOUT) && f->writable && dsp_writable() ? POLLOUT : 0;
|
||||
|
||||
if(f->ip && f->ip->type == T_DEV && f->ip->major == CONSOLE){
|
||||
int rev = 0;
|
||||
if((events & POLLIN) && f->readable && console_input_ready(f->ip ? f->ip->minor : 1))
|
||||
|
|
|
|||
|
|
@ -2794,6 +2794,10 @@ procdump(void)
|
|||
for(i=0; i<10 && pc[i] != 0; i++)
|
||||
cprintf(" %p", pc[i]);
|
||||
}
|
||||
// Where a thread that is not asleep last was in user space: the question
|
||||
// when a program spins without making a system call.
|
||||
if((p->state == RUNNABLE || p->state == RUNNING) && p->tf && p->pgdir)
|
||||
cprintf(" user rip %p rsp %p", p->tf->rip, p->tf->rsp);
|
||||
cprintf("\n");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,16 +34,25 @@ tmux
|
|||
mc
|
||||
ncdu
|
||||
fastfetch
|
||||
# Games. In the terminal: nethack, the BSD set, and two toys. On X: lbreakout2
|
||||
# (SDL 1.2) and the classic X clients.
|
||||
# Games. In the terminal: nethack, the BSD set, and two toys. On X:
|
||||
# lbreakouthd (SDL 2) and the classic X clients. Not lbreakout2: its stk.c
|
||||
# hands vsnprintf a size of 511 + the prefix length, and Alpine's fortified
|
||||
# headers trap on that before it loads its first image -- on Alpine too.
|
||||
nethack
|
||||
bsd-games
|
||||
cmatrix
|
||||
sl
|
||||
lbreakout2
|
||||
lbreakouthd
|
||||
xeyes
|
||||
xclock
|
||||
xcalc
|
||||
# A browser: NetSurf, on GTK 3 -- glib's threads, cairo, pango, TLS. The step
|
||||
# before WebKit (epiphany), which is `apk add` away.
|
||||
netsurf
|
||||
# curl's command line; libcurl is already here for NetSurf.
|
||||
curl
|
||||
# Sound for everything that speaks ALSA, SDL included: alsa-lib with the OSS
|
||||
# plugin, pointed at /dev/dsp by schos-base's /etc/asound.conf. SchOS has OSS,
|
||||
# not ALSA's kernel side, and SDL 3 has no OSS backend of its own.
|
||||
alsa-lib
|
||||
alsa-plugins
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# Generated by tools/posix/alpine-lock.sh from world. Do not edit;
|
||||
# change world and run `make alpine-lock`.
|
||||
# file sha256 repository
|
||||
alsa-lib-1.2.15.3-r0.apk 052de5639df635a03cfae1d7504ed4ed75c4e2d2cfff59efc116bb847c1f4ac4 main
|
||||
alsa-plugins-1.2.12-r2.apk 0ac6513add2a5268f67231b241473b4b7b43f5aad9f4674c900525e5152e0752 community
|
||||
at-spi2-core-2.60.5-r0.apk c9989ba9b2ff44f87a2106ca9560c2e554a56811d0fc2863b22741d576f1d84f main
|
||||
at-spi2-core-libs-2.60.5-r0.apk bcd4846311c8b19ad7e08ffc3cf726ddbe3d36bbc524386bda64758f5c5981a6 main
|
||||
avahi-libs-0.9_rc4-r0.apk 380d59c58cfce9cb5cf342cf73947749ca77a880f729970e2f508aaa6fc2f713 main
|
||||
|
|
@ -17,6 +19,8 @@ cairo-gobject-1.18.4-r1.apk ec302c75a24edaa4a2f867cfb5eec85cf30efe184044bab6e2b9
|
|||
c-ares-1.34.9-r0.apk 3539a3e11c1ef1516117d8179a546ac882086e94e0d78ad51d687b3fa2b3b5f2 main
|
||||
cmatrix-2.0-r2.apk d9f6bea3851492a6db2a9cd3f7e2df0f0756c6651a702b36ed2b887328ad1f00 community
|
||||
cups-libs-2.4.19-r1.apk 5739c568d90f23006d74ff82619e1a18447a6f698ae2af61970debe6658a7bea main
|
||||
curl-8.22.0-r0.apk 9262352e6341bb87e3047d1ca0b4051b2a9f4011409df25e4190e6be0334b860 main
|
||||
curl-zsh-completion-8.22.0-r0.apk 182a5f0afb5fd8786137f4610c065c305aa593fd18994df908c146335fb6011c main
|
||||
dbus-libs-1.16.2-r2.apk 105f6ba17454c587ca9345f9deb348012d1950588a22da42120c954d396c99b6 main
|
||||
e2fsprogs-libs-1.47.4-r0.apk 2e067395a6981b06cfd2cd3abf0e6c6574f7ef64d02e01f75fc5323d4a337fb9 main
|
||||
encodings-1.1.0-r0.apk f7c0eb762638d0249b4fbbe07a8e4ed6aab8e86b74c239c40e47a36219777d52 main
|
||||
|
|
@ -44,7 +48,8 @@ harfbuzz-13.2.1-r0.apk 01f7862265ef2256bbadd0fc998df3d1243dee606df3bd8284471cf39
|
|||
htop-3.5.3-r0.apk 6f6d444ca9a4133c2c13323ded04128783d59f924bcff0276d150a982904a2f3 main
|
||||
hwdata-pci-0.408-r0.apk fe61404e6d2bd1e04c021d53641c0ebce97911084d77994e43a288a1278686c1 main
|
||||
hwdata-usb-0.408-r0.apk 272d365cb05dd085dd8022154cf71e3f3b0aaddd41c1269eeaf939c9d38675c8 main
|
||||
lbreakout2-2.6.5-r2.apk 2c0f448af62e8138d9e612a858198f4bb906af33db50a32bb4bd468239521449 community
|
||||
lbreakouthd-1.2.2-r0.apk 4d7f78d866a7c544132e5f159d18f978515ac948517f2b8a6c625756bb81a3e0 community
|
||||
lbreakouthd-data-1.2.2-r0.apk 28ce98df27bfbb442fa397fbb9eb3eb5464e5b7b040ea034f207be52f4f4f07f community
|
||||
lcms2-2.19-r0.apk b4cb038af3c2f02bedd8456ae876c97ac0a9f29789add77871bfd6ce084ff136 main
|
||||
libatk-1.0-2.60.5-r0.apk ab5f603b17f0f0c5577d259b8263c5cbc6c7b3fd031f06ddc1e3e26417e1609a main
|
||||
libatk-bridge-2.0-2.60.5-r0.apk f5e38bc54bae780680134f7b96ce8e01ef24e98ae0ac67ded7229af07aa0b0f1 main
|
||||
|
|
@ -78,6 +83,7 @@ libpciaccess-0.19-r0.apk 03fdccd1e2a0825e7f7fabbac67212e05d271d31d2eff44cc92f707
|
|||
libpng-1.6.59-r0.apk d16aeb0597f2bc43e0583ac9ff779388ec160c3e22568b640122e94ba55be6d7 main
|
||||
libpsl-0.21.5-r3.apk ec665a344b6e887f00f0a47015cc732c3a281b69e84641dc8662ea234fde6cdb main
|
||||
librsvg-2.62.3-r0.apk e2aeb4a2b6b1ab20627dbb0e9136ea90f7120ec61412ed52f4335ee231361fde community
|
||||
libsamplerate-0.2.2-r3.apk d47765972e0f4b7c5585a5b27957ffe9bc63d4cc9729134fd876ee5d3f5f986d main
|
||||
libseccomp-2.6.1-r0.apk b22ccfe8cb072e4c8d3bc17222eeda132e270ce6258c27622acb193bf27b48e1 main
|
||||
libsharpyuv-1.6.0-r0.apk 34621ff53f43030d24d1224fe9cb53b1590ab614cbce6080005cde703e6c874a main
|
||||
libsm-1.2.6-r0.apk dd7eff58a46fd60e4afee9c7c1c3508920b0e1d6560129e59aee82329f68afc8 main
|
||||
|
|
@ -138,10 +144,11 @@ pango-1.57.1-r0.apk c616b62df908026ef413b338b9043e36706b05b6d035049394e5d14022e6
|
|||
pcre2-10.49-r0.apk 2919897914820f42a17a8d7d37df310d2dfad1c82ebe5f539cc5c765f916752a main
|
||||
pixman-0.46.4-r0.apk 378550cbe348dda9b0733014375dba87a89f0077c5f3dbeba7dce8dbcdda707e main
|
||||
pkgconf-2.5.1-r0.apk 20cc47b76b82e9424a35ecc4fdd7bd904ef49ada1684214910fae31bc0cab737 main
|
||||
sdl12-compat-1.2.76-r1.apk 124791270d613d1e33115625b95725487fe76ccfbc08638b8baa1b180287deb9 community
|
||||
sdl2-compat-2.32.68-r0.apk 0c4c1279446cba3c2065a3d3c2cacfa9eb4aeff507757c5a37193aaf028c5cf7 community
|
||||
sdl2_image-2.8.12-r0.apk 9d9f337de7fe25c363700701b8dd76ff4026de9c3d977e321ada08058b57eeda community
|
||||
sdl2_mixer-2.8.2-r0.apk 504b68ae9cf6705942853bb6977d3b341615c72066031e5aff54d0c6aca76964 community
|
||||
sdl2_ttf-2.24.0-r0.apk 0efcd7f03a1c70fc87706b0bb1e95b8747a1b796f089af56f296bf764ac8d988 community
|
||||
sdl3-3.4.10-r0.apk e9e4537d455f8b710a1f6fed7607c3cdb492fdc155684cf38e1b054bba809427 community
|
||||
sdl_mixer-1.2.12-r3.apk 9733a4e906b76f8bb5f246578ffc123a39a489fbfe60212abbaaaa0782e62e1c community
|
||||
shared-mime-info-2.4-r7.apk b0ed78b502cacfa2dfb6ff87aaef36a034258026873ab5b73f5e603d6623a147 main
|
||||
sl-5.02-r1.apk aa3ceb845953904ab61459f0f97416903c56a80b428f8bc320d879b9e58c1a57 community
|
||||
slang-2.3.3-r3.apk 96daf8e02cbcc0d4fd7163d087ae2a71a1aea279a0ac8d2a00a1612ccdccf644 main
|
||||
|
|
|
|||
|
|
@ -211,6 +211,32 @@ exec "$@"
|
|||
BEOF
|
||||
chmod 755 "$P/usr/bin/bwrap"
|
||||
|
||||
# ALSA's default device, through the OSS plugin to /dev/dsp. The plug in front
|
||||
# converts whatever a program plays to what every SchOS audio driver accepts:
|
||||
# 16-bit stereo at 48 kHz (AC'97 without variable rate plays nothing else, and
|
||||
# the plugin refuses a rate the device rounds by more than 1%).
|
||||
mkdir -p "$P/etc"
|
||||
cat > "$P/etc/asound.conf" <<'AEOF'
|
||||
# SchOS: ALSA plays through OSS's /dev/dsp (kernel/dev/audio.c).
|
||||
pcm.!default {
|
||||
type plug
|
||||
slave {
|
||||
pcm "dsp"
|
||||
format S16_LE
|
||||
channels 2
|
||||
rate 48000
|
||||
}
|
||||
}
|
||||
pcm.dsp {
|
||||
type oss
|
||||
device /dev/dsp
|
||||
}
|
||||
ctl.!default {
|
||||
type hw
|
||||
card 0
|
||||
}
|
||||
AEOF
|
||||
|
||||
# Mount points and runtime directories are alpine-baselayout's; an empty
|
||||
# directory here only duplicates it.
|
||||
for d in dev proc sys run tmp srv; do
|
||||
|
|
|
|||
Loading…
Reference in a new issue