Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/play/audio_out.c
T

167 lines
6.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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;
}