- meow_audiod:启动门控(/tmp/meow_boot_mode 非 meow exit 0)、IO/播放/路由三线程、
ring ~200ms、ALSA UAPI 独占直开、切率 mute-重配-恢复、抢占(后开赢+PREEMPTED)、
socket EOF 回收、xrun PREPARE 恢复;meow_route 轮询逻辑并入路由线程
- meow_audio CLI:status/tone/route/vol;services.d/audiod.conf(trigger=manual,on-fail)
- SDK meow_ui_audio.c 编进 meow_ui.a:懒连接/读线程分发 PREEMPTED 回调/EPIPE 标死/
无 audiod 返回 MEOW_ERR_NOENT 不隐式直开(MEOW_AUDIO_DIRECT=1 仅调试)
- launcher:确认 MeowOS 模式后 meow_svcd start audiod(double-fork best-effort 不阻塞)
- meow_route 退役:S89 预置换 audiod.conf + 清理旧 meow_route.conf(防拉起已删二进制
重启循环);fw_content.conf 删 meow_route、加 meow_audiod+meow_audio(md5 打包时填)
- 真机定位并修复 ALSA UAPI 两大坑(即 ncm 播放无声根因):
① hw_params 须全放行初始化(mask 全 1/interval [0,UINT_MAX]),memset 0 interval
等价只能取 0 → refine 必空 → HW_PARAMS 恒 EINVAL
② access/format mask 先清零再单点(pmask 只 OR),全 1 被 choose 成 MMAP_INTERLEAVED
→ write() 恒 EINVAL
- 附带修复:dev_write fd<0 防日志风暴丢 PCM 限频日志;SDK state 解析 app_id 截空白;
meow_ui.h 文档负错误码表述修正
- 验证:宿主 FILE sink 全 IPC 链+抢占+tone WAV 20/20,SDK 客户端 13/13;
qemu-mipsel 同过;真机 tone 44.1k/96k 切率 written=played、underrun=0、日志完整
- meow_ui.a 53a3edd2 → ncm 重链 d2031829(已热更到卡);launcher a2f932a1
310 lines
12 KiB
C
310 lines
12 KiB
C
#define _GNU_SOURCE
|
||
|
||
/* meow_audio.c — meow_audiod 用户侧 CLI(root 免鉴权,ssh 直呼)。
|
||
*
|
||
* 命令:
|
||
* meow_audio status 当前流/参数/帧数/underrun/路由/音量
|
||
* meow_audio tone [freq] [ms] [rate] 正弦测试音(默认 440Hz 500ms 44100;排查无声不须开 App)
|
||
* meow_audio route <0|2|3> 手动切路由(0=auto 2=3.5 3=4.4;调试/面板)
|
||
* meow_audio vol [0-100] 设置音量(无参=查询;v1 透传写 DAC numid=1/2 + settings.conf)
|
||
*
|
||
* 传输:AF_UNIX SOCK_STREAM 到 audiod socket(env MEOW_AUDIO_SOCK / --sock 覆盖,宿主/qemu)。
|
||
* 帧格式复用 meow_ipc.h 16B 头 + m_audio_* 负载。无 audiod → 连接失败/超时 → 打印友好错误并退出。
|
||
*
|
||
* 编译(mipsel 静态):build_audiod.sh cli。宿主编译:build_audiod.sh host。
|
||
*/
|
||
#include "meow_ipc.h"
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <errno.h>
|
||
#include <poll.h>
|
||
#include <math.h>
|
||
#include <time.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/un.h>
|
||
|
||
#define AUD_SOCK_DEFAULT "/usr/data/meow_audio.sock"
|
||
|
||
static char g_sock[108] = AUD_SOCK_DEFAULT;
|
||
static int g_fd = -1;
|
||
|
||
static int write_all(int fd, const uint8_t *p, size_t n)
|
||
{
|
||
size_t off = 0;
|
||
while (off < n) {
|
||
ssize_t w = write(fd, p + off, n - off);
|
||
if (w < 0) { if (errno == EINTR) continue; return -1; }
|
||
if (w == 0) return -1;
|
||
off += (size_t)w;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int read_all(int fd, uint8_t *p, size_t n)
|
||
{
|
||
size_t off = 0;
|
||
while (off < n) {
|
||
ssize_t r = read(fd, p + off, n - off);
|
||
if (r < 0) { if (errno == EINTR) continue; return -1; }
|
||
if (r == 0) return -1;
|
||
off += (size_t)r;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int connect_sock(void)
|
||
{
|
||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
if (fd < 0) return -1;
|
||
struct sockaddr_un a;
|
||
memset(&a, 0, sizeof a);
|
||
a.sun_family = AF_UNIX;
|
||
snprintf(a.sun_path, sizeof a.sun_path, "%s", g_sock);
|
||
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; }
|
||
return fd;
|
||
}
|
||
|
||
static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len)
|
||
{
|
||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||
uint32_t magic = MEOW_IPC_MAGIC;
|
||
uint16_t ver = MEOW_IPC_VERSION;
|
||
memcpy(hdr + 0, &magic, 4);
|
||
memcpy(hdr + 4, &ver, 2);
|
||
memcpy(hdr + 6, &type, 2);
|
||
memcpy(hdr + 8, &req_id, 4);
|
||
memcpy(hdr + 12, &len, 4);
|
||
if (write_all(fd, hdr, sizeof hdr) != 0) return -1;
|
||
if (len && payload && write_all(fd, payload, len) != 0) return -1;
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* 读一帧头部+负载;返回负载长(≥0),-1=断/坏帧/超时。 */
|
||
static int read_frame(int fd, uint16_t *type, uint8_t *rbuf, uint32_t rcap)
|
||
{
|
||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||
if (read_all(fd, hdr, sizeof hdr) != 0) return -1;
|
||
uint32_t magic, len; uint16_t ver;
|
||
memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2); memcpy(type, hdr + 6, 2);
|
||
memcpy(&len, hdr + 12, 4);
|
||
if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) return -1;
|
||
if (len > rcap) len = rcap;
|
||
if (len > 0 && read_all(fd, rbuf, len) != 0) return -1;
|
||
return (int)len;
|
||
}
|
||
|
||
/* 请求-响应:发一帧、读响应(带 3s 超时防挂)。返回响应负载长;-1=失败。 */
|
||
static int req_resp(uint16_t type, const void *pl, uint32_t len,
|
||
uint16_t *otype, uint8_t *obuf, uint32_t ocap)
|
||
{
|
||
if (send_frame(g_fd, type, 1, pl, len) != 0) return -1;
|
||
struct pollfd p;
|
||
p.fd = g_fd; p.events = POLLIN; p.revents = 0;
|
||
int r = poll(&p, 1, 3000);
|
||
if (r <= 0 || !(p.revents & POLLIN)) return -1;
|
||
return read_frame(g_fd, otype, obuf, ocap);
|
||
}
|
||
|
||
static int fmt_bps(int fmt)
|
||
{
|
||
switch (fmt) {
|
||
case AUDIO_FMT_S16: return 2;
|
||
case AUDIO_FMT_S24:
|
||
case AUDIO_FMT_S32:
|
||
case AUDIO_FMT_FLOAT: return 4;
|
||
default: return 2;
|
||
}
|
||
}
|
||
static const char *fmt_name(int fmt)
|
||
{
|
||
switch (fmt) {
|
||
case AUDIO_FMT_S16: return "S16";
|
||
case AUDIO_FMT_S24: return "S24";
|
||
case AUDIO_FMT_S32: return "S32";
|
||
case AUDIO_FMT_FLOAT: return "FLOAT";
|
||
default: return "?";
|
||
}
|
||
}
|
||
|
||
static int cmd_status(void)
|
||
{
|
||
uint8_t rbuf[MEOW_IPC_MAX];
|
||
uint16_t rt;
|
||
int n = req_resp(MSG_AUDIO_QUERY, NULL, 0, &rt, rbuf, sizeof rbuf);
|
||
if (n < 0) { fprintf(stderr, "meow_audio: no audiod (%s), status unavailable\n", g_sock); return 1; }
|
||
if (rt != MSG_AUDIO_STATE) { fprintf(stderr, "meow_audio: unexpected reply type 0x%04x\n", rt); return 1; }
|
||
rbuf[n < (int)sizeof rbuf - 1 ? n : (int)sizeof rbuf - 1] = 0;
|
||
fputs((const char *)rbuf, stdout);
|
||
return 0;
|
||
}
|
||
|
||
static int cmd_route(int port)
|
||
{
|
||
m_audio_route_t r; r.port = (uint32_t)port;
|
||
uint8_t rbuf[256]; uint16_t rt;
|
||
int n = req_resp(MSG_AUDIO_ROUTE, &r, sizeof r, &rt, rbuf, sizeof rbuf);
|
||
if (n < 0) { fprintf(stderr, "meow_audio: no audiod (%s)\n", g_sock); return 1; }
|
||
printf("route %d -> %s\n", port, (port == 0) ? "auto" : (port == 3 ? "4.4" : "3.5"));
|
||
return 0;
|
||
}
|
||
|
||
static int cmd_vol(int pct)
|
||
{
|
||
m_audio_volume_t v; v.pct = (int32_t)pct;
|
||
uint8_t rbuf[256]; uint16_t rt;
|
||
int n = req_resp(MSG_AUDIO_VOLUME, &v, sizeof v, &rt, rbuf, sizeof rbuf);
|
||
if (n < 0 || rt != MSG_ACK) { fprintf(stderr, "meow_audio: no audiod (%s)\n", g_sock); return 1; }
|
||
m_ack_t a; memcpy(&a, rbuf, sizeof a);
|
||
printf("vol %u%%%s\n", (unsigned)a.status, (pct >= 0 ? " (set)" : " (query)"));
|
||
return 0;
|
||
}
|
||
|
||
/* 正弦生成:填 nframes 帧(grant 交织格式)到 buf(上限 bufcap,帧对齐)。
|
||
* start 为起始帧号;返回实际写入字节数。 */
|
||
static size_t gen_sine(uint8_t *buf, size_t bufcap, int grant_fmt, int grant_ch,
|
||
double freq, int rate, size_t start, size_t nframes, double amp)
|
||
{
|
||
int bps = fmt_bps(grant_fmt);
|
||
size_t fs = (size_t)grant_ch * (size_t)bps;
|
||
if (fs == 0) fs = 1;
|
||
size_t n = (bufcap / fs) * fs;
|
||
if (n / fs > nframes) n = nframes * fs;
|
||
size_t done = 0;
|
||
for (size_t i = 0; i < n / fs; i++) {
|
||
double ph = 2.0 * M_PI * freq * (double)(start + i) / (double)rate;
|
||
double s = sin(ph) * amp;
|
||
long v = (long)(s * 32767.0);
|
||
if (v > 32767) v = 32767;
|
||
if (v < -32768) v = -32768;
|
||
long vs = v; /* S16 值 */
|
||
if (grant_fmt == AUDIO_FMT_S32 || grant_fmt == AUDIO_FMT_S24) vs = v << 16;
|
||
for (int c = 0; c < grant_ch; c++) {
|
||
if (grant_fmt == AUDIO_FMT_S16) {
|
||
int16_t xx = (int16_t)v;
|
||
memcpy(buf + done, &xx, 2); done += 2;
|
||
} else {
|
||
int32_t xx = (int32_t)(vs);
|
||
memcpy(buf + done, &xx, 4); done += 4;
|
||
}
|
||
}
|
||
}
|
||
return done;
|
||
}
|
||
|
||
static int cmd_tone(int freq, int ms, int rate)
|
||
{
|
||
m_audio_open_t o;
|
||
memset(&o, 0, sizeof o);
|
||
snprintf(o.app_id, sizeof o.app_id, "audio-cli");
|
||
o.rate = (uint32_t)rate;
|
||
o.ch = 2;
|
||
o.fmt = AUDIO_FMT_S16;
|
||
uint8_t rbuf[512]; uint16_t rt;
|
||
int n = req_resp(MSG_AUDIO_OPEN, &o, sizeof o, &rt, rbuf, sizeof rbuf);
|
||
if (n < 0) { fprintf(stderr, "meow_audio: no audiod (%s) — 音频服务不可用\n", g_sock); return 1; }
|
||
if (rt != MSG_AUDIO_OPEN_ACK) { fprintf(stderr, "meow_audio: unexpected reply 0x%04x\n", rt); return 1; }
|
||
m_audio_open_ack_t a; memcpy(&a, rbuf, sizeof a);
|
||
if (a.err != MEOW_OK) {
|
||
fprintf(stderr, "meow_audio: open failed (err=%d): %s\n", a.err,
|
||
a.err == MEOW_ERR_NODEV ? "no usable PCM device" :
|
||
a.err == MEOW_ERR_INVAL ? "invalid params" : "error");
|
||
return 1;
|
||
}
|
||
printf("tone: sid=%u grant=%uHz/%uch/%s\n", (unsigned)a.stream_id,
|
||
(unsigned)a.grant_rate, (unsigned)a.grant_ch, fmt_name((int)a.grant_fmt));
|
||
|
||
double amp = 0.3;
|
||
size_t total_frames = (size_t)((uint64_t)ms * (uint64_t)rate / 1000);
|
||
uint8_t pcm[MEOW_IPC_MAX];
|
||
size_t fs = (size_t)a.grant_ch * (size_t)fmt_bps((int)a.grant_fmt);
|
||
if (fs == 0) fs = 1;
|
||
size_t sent_frames = 0;
|
||
while (sent_frames < total_frames) {
|
||
size_t want = total_frames - sent_frames;
|
||
if (want > (size_t)(sizeof pcm / fs)) want = sizeof pcm / fs;
|
||
size_t by = gen_sine(pcm, sizeof pcm, (int)a.grant_fmt, (int)a.grant_ch,
|
||
(double)freq, rate, sent_frames, want, amp);
|
||
if (by == 0) break;
|
||
if (send_frame(g_fd, MSG_AUDIO_WRITE, 0, pcm, (uint32_t)by) != 0) {
|
||
fprintf(stderr, "meow_audio: write failed (audiod died?)\n");
|
||
return 1;
|
||
}
|
||
sent_frames += by / fs;
|
||
}
|
||
|
||
/* 停止 + 等待排空(WAV 完整落盘 / 真实 DAC 播放完) */
|
||
m_audio_stream_t s; s.stream_id = a.stream_id;
|
||
req_resp(MSG_AUDIO_STOP, &s, sizeof s, &rt, rbuf, sizeof rbuf);
|
||
uint64_t deadline_ms = (uint64_t)ms + 3000;
|
||
uint64_t t0 = 0;
|
||
{
|
||
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
t0 = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||
}
|
||
for (;;) {
|
||
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
uint64_t now = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||
if (now - t0 > deadline_ms) break;
|
||
req_resp(MSG_AUDIO_QUERY, NULL, 0, &rt, rbuf, sizeof rbuf);
|
||
if (rt == MSG_AUDIO_STATE) {
|
||
if (strstr((const char *)rbuf, "active=0")) break;
|
||
}
|
||
usleep(100000); /* 100ms 轮询 */
|
||
}
|
||
printf("tone done: sent %zu frames\n", sent_frames);
|
||
close(g_fd);
|
||
g_fd = -1;
|
||
return 0;
|
||
}
|
||
|
||
static void usage(const char *argv0)
|
||
{
|
||
fprintf(stderr, "usage: %s status | tone [freq] [ms] [rate] | route <0|2|3> | vol [0-100]\n"
|
||
" env: MEOW_AUDIO_SOCK (socket 缺省 %s)\n", argv0, AUD_SOCK_DEFAULT);
|
||
}
|
||
|
||
int main(int argc, char **argv)
|
||
{
|
||
const char *e = getenv("MEOW_AUDIO_SOCK");
|
||
if (e && e[0]) snprintf(g_sock, sizeof g_sock, "%s", e);
|
||
for (int i = 1; i < argc; i++) {
|
||
if (!strcmp(argv[i], "--sock") && i + 1 < argc) { snprintf(g_sock, sizeof g_sock, "%s", argv[++i]); }
|
||
else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { usage(argv[0]); return 0; }
|
||
}
|
||
if (argc < 2) { usage(argv[0]); return 2; }
|
||
|
||
g_fd = connect_sock();
|
||
if (g_fd < 0 && strcmp(argv[1], "status") != 0 && strcmp(argv[1], "vol") != 0
|
||
&& strcmp(argv[1], "route") != 0 && strcmp(argv[1], "tone") != 0) {
|
||
/* 未知命令用 status 兜底说明 */
|
||
}
|
||
/* 连接失败:status/vol/route/tone 各自报错;未知命令直接 usage */
|
||
const char *cmd = argv[1];
|
||
if (!strcmp(cmd, "status")) {
|
||
return cmd_status();
|
||
} else if (!strcmp(cmd, "route")) {
|
||
if (argc < 3) { usage(argv[0]); return 2; }
|
||
int port = atoi(argv[2]);
|
||
if (port != 0 && port != 2 && port != 3) { fprintf(stderr, "route: port 0/2/3\n"); return 2; }
|
||
if (g_fd < 0) { fprintf(stderr, "meow_audio: no audiod (%s)\n", g_sock); return 1; }
|
||
int rc = cmd_route(port); close(g_fd); return rc;
|
||
} else if (!strcmp(cmd, "vol")) {
|
||
if (g_fd < 0) { fprintf(stderr, "meow_audio: no audiod (%s)\n", g_sock); return 1; }
|
||
int pct = (argc >= 3) ? atoi(argv[2]) : -1;
|
||
if (pct > 100) pct = 100;
|
||
int rc = cmd_vol(pct); close(g_fd); return rc;
|
||
} else if (!strcmp(cmd, "tone")) {
|
||
int freq = (argc >= 3) ? atoi(argv[2]) : 440;
|
||
int ms = (argc >= 4) ? atoi(argv[3]) : 500;
|
||
int rate = (argc >= 5) ? atoi(argv[4]) : 44100;
|
||
if (freq <= 0 || ms <= 0 || rate <= 0) { fprintf(stderr, "tone: bad params\n"); return 2; }
|
||
if (g_fd < 0) { fprintf(stderr, "meow_audio: no audiod (%s) — 音频服务不可用\n", g_sock); return 1; }
|
||
return cmd_tone(freq, ms, rate);
|
||
}
|
||
usage(argv[0]);
|
||
return 2;
|
||
}
|