ncm 1e331964 (text 764KB); 0 warning; 宿主离屏6页截图目检+真实QR可扫+qemu-mipsel干净启动+解码446xRT 真机待联调: ALSA时序/803扫码闭环/媒体会话联动/gbk大字库渲染
167 lines
6.3 KiB
C
167 lines
6.3 KiB
C
/* play/audio_out.c — 音频输出抽象:ALSA 直开 hw:0,0(44.1k/16bit/立体声)或 FILE/NULL sink。
|
||
*
|
||
* 设备(mipsel musl 无 libasound):用 ALSA 内核 UAPI(sound/asound.h)直开 /dev/snd/pcmC0D0p,
|
||
* 非 mmap RW 模式下 ioctl 设 hw_params + write() 写交织帧。布局/时序真机待验证(本层只保证跨编译 +
|
||
* 宿主回退正确)。
|
||
* 宿主/qemu 验证:无 /dev/snd/pcmC0D0p → 回退 FILE sink(写 WAV,env NCM_AUDIO_OUT 指定路径,
|
||
* 缺省 /tmp/ncm_out.wav)或 NULL(env NCM_AUDIO_SINK=null,丢弃,仅测解码 RT 用)。
|
||
* 线程模型:仅由 player_manager 播放线程调用(单写者);audio_out_close 可被 UI 线程调用打断阻塞写。
|
||
*/
|
||
#include "../ncm_app_internal.h"
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
#include <errno.h>
|
||
|
||
#ifdef __linux__
|
||
#include <sound/asound.h>
|
||
#include <sys/ioctl.h>
|
||
#define NCM_HAVE_ALSA 1
|
||
#endif
|
||
|
||
enum { SINK_ALSADEV = 0, SINK_FILE = 1, SINK_NULL = 2 };
|
||
static int g_sink = -1;
|
||
static int g_fd = -1; /* alsa or file fd */
|
||
static int g_rate, g_ch, g_bits;
|
||
static const char *g_path = NULL;
|
||
|
||
#define NCM_DEFAULT_OUT "/tmp/ncm_out.wav"
|
||
|
||
void audio_out_set_target(int ms) { (void)ms; }
|
||
int audio_out_buffered_ms(void) { return 0; }
|
||
|
||
#ifdef NCM_HAVE_ALSA
|
||
/* ALSA 非 mmap 直装:开设备 → hw_params(access=rw_interleaved, S16_LE, rate, 2ch, s16)→ write。
|
||
* 注意:musl 的 sound/asound.h 中 masks/intervals 是**重索引**数组:
|
||
* masks[k] ↔ hw_param (FIRST_MASK + k); intervals[k] ↔ hw_param (FIRST_INTERVAL + k)。
|
||
* 故访问时须减 FIRST_MASK/FIRST_INTERVAL,否则越界/错位。 */
|
||
static void pmask(struct snd_mask *m, unsigned bit) { m->bits[bit / 32] |= 1u << (bit % 32); }
|
||
static void pivset(struct snd_interval *iv, unsigned val)
|
||
{
|
||
iv->min = iv->max = val;
|
||
iv->integer = 1;
|
||
iv->openmin = iv->openmax = 0;
|
||
iv->empty = 0;
|
||
}
|
||
static int alsa_open(int rate, int ch, int bits)
|
||
{
|
||
if (bits != 16) return -1;
|
||
int fd = open("/dev/snd/pcmC0D0p", O_RDWR);
|
||
if (fd < 0) return -1;
|
||
struct snd_pcm_hw_params p;
|
||
memset(&p, 0, sizeof p);
|
||
p.flags = 0;
|
||
pmask(&p.masks[SNDRV_PCM_HW_PARAM_ACCESS - SNDRV_PCM_HW_PARAM_FIRST_MASK],
|
||
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
|
||
pmask(&p.masks[SNDRV_PCM_HW_PARAM_FORMAT - SNDRV_PCM_HW_PARAM_FIRST_MASK],
|
||
SNDRV_PCM_FORMAT_S16_LE);
|
||
pmask(&p.masks[SNDRV_PCM_HW_PARAM_SUBFORMAT - SNDRV_PCM_HW_PARAM_FIRST_MASK],
|
||
SNDRV_PCM_SUBFORMAT_STD);
|
||
pivset(&p.intervals[SNDRV_PCM_HW_PARAM_CHANNELS - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL],
|
||
(unsigned)ch);
|
||
pivset(&p.intervals[SNDRV_PCM_HW_PARAM_RATE - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL],
|
||
(unsigned)rate);
|
||
p.rmask = ~0u;
|
||
p.info = 0;
|
||
p.msbits = 0;
|
||
p.rate_num = 0;
|
||
p.rate_den = 0;
|
||
if (ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &p) != 0) { close(fd); return -1; }
|
||
ioctl(fd, SNDRV_PCM_IOCTL_PREPARE, 0);
|
||
return fd;
|
||
}
|
||
#endif
|
||
|
||
/* 写 WAV 头(44 字节标准 PCM 布局:RIFF/WAVE/fmt(16)/data)。
|
||
* fmt(16) = format(2) ch(2) sample_rate(4) byte_rate(4) block_align(2) bits(2)。 */
|
||
static void file_write_header(int fd, int rate, int ch, int bits, uint32_t data_len)
|
||
{
|
||
unsigned char h[44];
|
||
memset(h, 0, sizeof h);
|
||
memcpy(h+0, "RIFF", 4);
|
||
uint32_t le = 36 + data_len; memcpy(h+4, &le, 4);
|
||
memcpy(h+8, "WAVE", 4);
|
||
memcpy(h+12, "fmt ", 4);
|
||
le = 16; memcpy(h+16, &le, 4);
|
||
uint16_t s16 = 1; memcpy(h+20, &s16, 2); /* PCM */
|
||
s16 = (uint16_t)ch; memcpy(h+22, &s16, 2);
|
||
le = (uint32_t)rate; memcpy(h+24, &le, 4); /* sample rate */
|
||
le = (uint32_t)(rate * ch * bits / 8); memcpy(h+28, &le, 4); /* byte rate */
|
||
s16 = (uint16_t)(ch * bits / 8); memcpy(h+32, &s16, 2); /* block align */
|
||
s16 = (uint16_t)bits; memcpy(h+34, &s16, 2); /* bits/sample */
|
||
memcpy(h+36, "data", 4);
|
||
le = data_len; memcpy(h+40, &le, 4);
|
||
if (write(fd, h, 44) != 44) { /* 忽略 */ }
|
||
}
|
||
|
||
int audio_out_open(int sample_rate, int channels, int bits)
|
||
{
|
||
g_rate = sample_rate; g_ch = channels; g_bits = bits;
|
||
const char *env = getenv("NCM_AUDIO_SINK");
|
||
g_path = getenv("NCM_AUDIO_OUT");
|
||
if (!g_path || !g_path[0]) g_path = NCM_DEFAULT_OUT;
|
||
|
||
/* 显式 null(测 RT) */
|
||
if (env && strcmp(env, "null") == 0) { g_sink = SINK_NULL; g_fd = -1; return 0; }
|
||
|
||
/* 尝试 ALSA(仅未显式指定 file;宿主无设备自动回退) */
|
||
if (!(env && strcmp(env, "file") == 0)) {
|
||
#ifdef NCM_HAVE_ALSA
|
||
int fd = alsa_open(sample_rate, channels, bits);
|
||
if (fd >= 0) { g_sink = SINK_ALSADEV; g_fd = fd; return 0; }
|
||
#endif
|
||
}
|
||
/* FILE sink */
|
||
int fd = open(g_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
if (fd < 0) { g_sink = SINK_NULL; g_fd = -1; return 0; } /* 无路可写 → null */
|
||
file_write_header(fd, sample_rate, channels, bits, 0);
|
||
g_sink = SINK_FILE;
|
||
g_fd = fd;
|
||
return 0;
|
||
}
|
||
|
||
int audio_out_write(const short *samples, int frames)
|
||
{
|
||
if (g_app_quit) return -1;
|
||
if (g_sink == SINK_NULL || g_sink < 0) return frames;
|
||
size_t n = (size_t)frames * (size_t)g_ch * (size_t)(g_bits / 8);
|
||
if (g_sink == SINK_ALSADEV) {
|
||
/* 阻塞尽力写满 - EINTR/EAGAIN 重试;错误返回 */
|
||
const unsigned char *p = (const unsigned char *)samples;
|
||
size_t off = 0;
|
||
while (off < n) {
|
||
ssize_t w = write(g_fd, p + off, n - off);
|
||
if (w > 0) { off += (size_t)w; continue; }
|
||
if (w < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||
return -1;
|
||
}
|
||
return frames;
|
||
}
|
||
if (g_sink == SINK_FILE) {
|
||
if (write(g_fd, samples, n) != (ssize_t)n) return -1;
|
||
return frames;
|
||
}
|
||
return frames;
|
||
}
|
||
|
||
void audio_out_close(void)
|
||
{
|
||
if (g_sink == SINK_FILE && g_fd >= 0) {
|
||
/* 回填:offset 4 = RIFF 总长-36;offset 40 = data 长度 */
|
||
off_t end = lseek(g_fd, 0, SEEK_END);
|
||
if (end > 44) {
|
||
uint32_t data_len = (uint32_t)(end - 44);
|
||
lseek(g_fd, 4, SEEK_SET);
|
||
uint32_t le = 36 + data_len;
|
||
if (write(g_fd, &le, 4) != 4) { }
|
||
lseek(g_fd, 40, SEEK_SET);
|
||
if (write(g_fd, &data_len, 4) != 4) { }
|
||
}
|
||
}
|
||
if (g_fd >= 0) close(g_fd);
|
||
g_fd = -1;
|
||
g_sink = -1;
|
||
}
|