Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: restore hot-mic with correct prerequisites

Now that we know the EIO root causes (HDMI audio + 6-period playback
buffer), hot-mic can work safely with:
- No HDMI PCM open in audio_init
- 3-period playback buffer (original value)
- Capture mixer enabled in capture thread (not audio_init)
- ALSA default period/buffer (no explicit setting)

This gives instant recording with zero device-open latency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+86 -39
+86 -39
fedac/native/src/audio.c
··· 1004 1004 audio->target_fx_mix = mix; 1005 1005 } 1006 1006 1007 - // --- Thread-per-recording capture (original working approach) --- 1008 - // Opens device fresh each recording, reads directly, closes on stop. 1009 - // Hot-mic was tried but causes EIO on some HDA codecs (11e Yoga Gen 5). 1007 + // --- Hot-mic capture thread --- 1008 + // Device opens once (on wave-enter), stays running. Always reads to keep 1009 + // ALSA happy and the level meter live. Only writes to sample_buf when 1010 + // recording flag is set. Instant recording with zero device-open latency. 1011 + // 1012 + // IMPORTANT: HDMI audio must be DISABLED in audio_init and playback buffer 1013 + // must be 3 periods (not 6) — otherwise the HDA controller runs out of 1014 + // streams and capture gets EIO. 1010 1015 static void *capture_thread_func(void *arg) { 1011 1016 ACAudio *audio = (ACAudio *)arg; 1012 1017 snd_pcm_t *cap = NULL; 1013 1018 1014 - // Try to open capture device 1015 1019 const char *devices[] = {"hw:0,0", "hw:1,0", "hw:0,6", "hw:0,7", 1016 1020 "plughw:0,0", "plughw:1,0", "default", NULL}; 1017 1021 for (int i = 0; devices[i]; i++) { ··· 1026 1030 snprintf(audio->mic_last_error, sizeof(audio->mic_last_error), 1027 1031 "no capture device found"); 1028 1032 ac_log("[mic] no capture device found\n"); 1029 - audio->recording = 0; 1033 + audio->mic_hot = 0; 1030 1034 return NULL; 1031 1035 } 1032 1036 1033 - // Configure capture — NO period/buffer setting, use ALSA defaults 1037 + // Configure — NO period/buffer setting (ALSA defaults work on this HDA) 1034 1038 snd_pcm_hw_params_t *hw; 1035 1039 snd_pcm_hw_params_alloca(&hw); 1036 1040 snd_pcm_hw_params_any(cap, hw); ··· 1051 1055 "failed to configure capture"); 1052 1056 ac_log("[mic] failed to configure capture\n"); 1053 1057 snd_pcm_close(cap); 1054 - audio->recording = 0; 1058 + audio->mic_hot = 0; 1055 1059 return NULL; 1056 1060 } 1057 1061 1058 - // Enable capture mixer controls NOW (after PCM open, in the capture thread) 1059 - // Must NOT be done in audio_init — that causes EIO on some HDA codecs. 1062 + // Enable capture mixer (safe here — after PCM open, in capture thread) 1060 1063 { 1061 1064 int cnum = 0; 1062 1065 const char *d = audio->mic_device; ··· 1090 1093 } 1091 1094 1092 1095 audio->sample_rate = rate; 1093 - audio->sample_write_pos = 0; 1094 1096 audio->mic_connected = 1; 1095 - ac_log("[mic] recording at %u Hz, %u ch\n", rate, channels); 1097 + ac_log("[mic] hot-mic running at %u Hz, %u ch\n", rate, channels); 1096 1098 1097 - int16_t buf[1024 * 2]; // max stereo 1098 - float peak = 0.0f; 1099 - while (audio->recording && audio->sample_write_pos < audio->sample_max_len) { 1099 + int16_t buf[1024 * 2]; 1100 + while (audio->mic_hot) { 1100 1101 int n = snd_pcm_readi(cap, buf, 512); 1101 1102 if (n < 0) { 1102 1103 n = snd_pcm_recover(cap, n, 0); 1103 - if (n < 0) break; 1104 + if (n < 0) { 1105 + snprintf(audio->mic_last_error, sizeof(audio->mic_last_error), 1106 + "capture read failed: %s", snd_strerror(n)); 1107 + ac_log("[mic] capture read failed: %s\n", snd_strerror(n)); 1108 + break; 1109 + } 1104 1110 continue; 1105 1111 } 1106 - for (int s = 0; s < n && audio->sample_write_pos < audio->sample_max_len; s++) { 1112 + 1113 + float peak = 0.0f; 1114 + for (int s = 0; s < n; s++) { 1107 1115 float sample; 1108 1116 if (channels == 1) { 1109 1117 sample = buf[s] / 32768.0f; ··· 1112 1120 } 1113 1121 float abs_s = fabsf(sample); 1114 1122 if (abs_s > peak) peak = abs_s; 1115 - audio->sample_buf[audio->sample_write_pos++] = sample; 1123 + 1124 + // Always write to ring buffer 1125 + audio->mic_ring[audio->mic_ring_pos % audio->sample_max_len] = sample; 1126 + audio->mic_ring_pos++; 1127 + 1128 + // Direct-write when recording 1129 + if (audio->recording && audio->sample_write_pos < audio->sample_max_len) { 1130 + audio->sample_buf[audio->sample_write_pos++] = sample; 1131 + } 1132 + } 1133 + audio->mic_level = peak; 1134 + 1135 + if (audio->recording && audio->sample_write_pos >= audio->sample_max_len) { 1136 + audio->sample_len = audio->sample_write_pos; 1137 + audio->recording = 0; 1138 + ac_log("[mic] recording buffer full (%d samples)\n", audio->sample_len); 1116 1139 } 1117 - audio->mic_level = peak; // update level meter live 1118 1140 } 1119 1141 1120 - audio->sample_len = audio->sample_write_pos; 1121 - ac_log("[mic] recorded %d samples (%.2fs) peak=%.4f\n", 1122 - audio->sample_len, (double)audio->sample_len / audio->sample_rate, peak); 1142 + ac_log("[mic] hot-mic thread exiting, device=%s\n", audio->mic_device); 1123 1143 snd_pcm_close(cap); 1124 1144 audio->mic_connected = 0; 1125 1145 audio->recording = 0; ··· 1127 1147 } 1128 1148 1129 1149 int audio_mic_open(ACAudio *audio) { 1130 - // No-op in thread-per-recording mode — device opens on rec start 1131 - if (!audio) return -1; 1150 + if (!audio || audio->mic_hot || audio->capture_thread_running) return -1; 1132 1151 audio->mic_hot = 1; 1133 - ac_log("[mic] mic ready (thread-per-recording mode)\n"); 1152 + audio->capture_thread_running = 1; 1153 + audio->mic_last_error[0] = 0; 1154 + ac_log("[mic] opening hot-mic\n"); 1155 + if (pthread_create(&audio->capture_thread, NULL, capture_thread_func, audio) != 0) { 1156 + audio->mic_hot = 0; 1157 + audio->capture_thread_running = 0; 1158 + ac_log("[mic] failed to create capture thread\n"); 1159 + return -1; 1160 + } 1134 1161 return 0; 1135 1162 } 1136 1163 ··· 1138 1165 if (!audio) return; 1139 1166 audio->recording = 0; 1140 1167 audio->mic_hot = 0; 1141 - ac_log("[mic] mic closed\n"); 1168 + if (audio->capture_thread_running) { 1169 + pthread_join(audio->capture_thread, NULL); 1170 + audio->capture_thread_running = 0; 1171 + } 1172 + ac_log("[mic] hot-mic closed\n"); 1142 1173 } 1143 1174 1144 1175 int audio_mic_start(ACAudio *audio) { 1145 1176 if (!audio || audio->recording) return -1; 1146 - audio->recording = 1; 1147 - audio->sample_len = 0; 1177 + if (!audio->mic_hot) { 1178 + int rc = audio_mic_open(audio); 1179 + if (rc != 0) return rc; 1180 + } 1148 1181 // Kill any playing sample voices 1149 1182 for (int i = 0; i < AUDIO_MAX_SAMPLE_VOICES; i++) 1150 1183 audio->sample_voices[i].active = 0; 1151 - if (pthread_create(&audio->capture_thread, NULL, capture_thread_func, audio) != 0) { 1152 - audio->recording = 0; 1153 - return -1; 1154 - } 1155 - pthread_detach(audio->capture_thread); 1156 - ac_log("[mic] recording started\n"); 1184 + audio->rec_start_ring_pos = audio->mic_ring_pos; 1185 + audio->sample_len = 0; 1186 + audio->sample_write_pos = 0; 1187 + __sync_synchronize(); 1188 + audio->recording = 1; 1189 + ac_log("[mic] recording started (instant), ring_pos=%d\n", audio->rec_start_ring_pos); 1157 1190 return 0; 1158 1191 } 1159 1192 1160 1193 int audio_mic_stop(ACAudio *audio) { 1161 1194 if (!audio) return 0; 1162 1195 audio->recording = 0; 1163 - // Wait for capture thread to finish writing sample_len (ALSA defaults 1164 - // can have large periods so the last snd_pcm_readi may block ~700ms) 1165 - for (int i = 0; i < 500 && audio->mic_connected; i++) { 1166 - usleep(2000); // 2ms per iter, max 1s 1196 + __sync_synchronize(); 1197 + 1198 + int direct_len = audio->sample_write_pos; 1199 + if (direct_len > 0) { 1200 + audio->sample_len = direct_len; 1201 + ac_log("[mic] recording stopped (direct), sample_len=%d sample_rate=%u\n", 1202 + audio->sample_len, audio->sample_rate); 1203 + } else { 1204 + // Fallback: extract from ring buffer 1205 + int start = audio->rec_start_ring_pos; 1206 + int end = audio->mic_ring_pos; 1207 + int len = end - start; 1208 + if (len < 0) len = 0; 1209 + if (len > audio->sample_max_len) len = audio->sample_max_len; 1210 + for (int i = 0; i < len; i++) { 1211 + audio->sample_buf[i] = audio->mic_ring[(start + i) % audio->sample_max_len]; 1212 + } 1213 + audio->sample_len = len; 1214 + ac_log("[mic] recording stopped (ring), sample_len=%d ring_span=%d sample_rate=%u\n", 1215 + audio->sample_len, end - start, audio->sample_rate); 1167 1216 } 1168 - ac_log("[mic] recording stopped, sample_len=%d sample_rate=%u\n", 1169 - audio->sample_len, audio->sample_rate); 1170 1217 return audio->sample_len; 1171 1218 } 1172 1219