Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

fix: restore thread-per-recording capture (original working approach)

Hot-mic architecture causes EIO on 11e Yoga Gen 5 HDA codec regardless
of period/buffer settings or mixer configuration. The original approach
from 90d8beeee (fresh thread per recording, ALSA defaults, no period/
buffer tuning) worked on this hardware.

This reverts capture to: open device → configure → read → close per
recording session. Higher latency on rec start but actually works.

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

+32 -149
+32 -149
fedac/native/src/audio.c
··· 1057 1057 audio->target_fx_mix = mix; 1058 1058 } 1059 1059 1060 - // --- Hot-mic capture thread --- 1061 - // Device stays open while mic_hot is set; audio is always read to keep 1062 - // ALSA happy and the level meter live, but only written to sample_buf 1063 - // when recording is true. This eliminates the device-open latency on 1064 - // every rec press. 1060 + // --- Thread-per-recording capture (original working approach) --- 1061 + // Opens device fresh each recording, reads directly, closes on stop. 1062 + // Hot-mic was tried but causes EIO on some HDA codecs (11e Yoga Gen 5). 1065 1063 static void *capture_thread_func(void *arg) { 1066 1064 ACAudio *audio = (ACAudio *)arg; 1067 1065 snd_pcm_t *cap = NULL; ··· 1070 1068 const char *devices[] = {"hw:0,0", "hw:1,0", "hw:0,6", "hw:0,7", 1071 1069 "plughw:0,0", "plughw:1,0", "default", NULL}; 1072 1070 for (int i = 0; devices[i]; i++) { 1073 - int open_rc = snd_pcm_open(&cap, devices[i], SND_PCM_STREAM_CAPTURE, 0); 1074 - if (open_rc == 0) { 1071 + if (snd_pcm_open(&cap, devices[i], SND_PCM_STREAM_CAPTURE, 0) == 0) { 1075 1072 snprintf(audio->mic_device, sizeof(audio->mic_device), "%s", devices[i]); 1076 - audio->mic_last_error[0] = 0; 1077 1073 ac_log("[mic] opened capture device: %s\n", devices[i]); 1078 1074 break; 1079 1075 } 1080 - ac_log("[mic] open failed %s: %s\n", devices[i], snd_strerror(open_rc)); 1081 1076 cap = NULL; 1082 1077 } 1083 1078 if (!cap) { 1084 1079 snprintf(audio->mic_last_error, sizeof(audio->mic_last_error), 1085 1080 "no capture device found"); 1086 - audio->mic_connected = 0; 1087 - audio->mic_level = 0.0f; 1088 1081 ac_log("[mic] no capture device found\n"); 1089 - audio->mic_hot = 0; 1082 + audio->recording = 0; 1090 1083 return NULL; 1091 1084 } 1092 1085 1093 - // Configure capture 1086 + // Configure capture — NO period/buffer setting, use ALSA defaults 1094 1087 snd_pcm_hw_params_t *hw; 1095 1088 snd_pcm_hw_params_alloca(&hw); 1096 1089 snd_pcm_hw_params_any(cap, hw); 1097 1090 snd_pcm_hw_params_set_access(cap, hw, SND_PCM_ACCESS_RW_INTERLEAVED); 1098 1091 snd_pcm_hw_params_set_format(cap, hw, SND_PCM_FORMAT_S16_LE); 1099 1092 1100 - // Try mono, fall back to stereo 1101 1093 unsigned int channels = 1; 1102 1094 if (snd_pcm_hw_params_set_channels(cap, hw, 1) < 0) { 1103 1095 channels = 2; ··· 1107 1099 unsigned int rate = 48000; 1108 1100 snd_pcm_hw_params_set_rate_near(cap, hw, &rate, NULL); 1109 1101 1110 - // Set reasonable period/buffer (ALSA defaults can be 32768/1M which 1111 - // blocks forever). Do NOT touch mixer after hw_params — that causes EIO. 1112 - snd_pcm_uframes_t period_frames = 1024; 1113 - snd_pcm_hw_params_set_period_size_near(cap, hw, &period_frames, NULL); 1114 - snd_pcm_uframes_t buffer_frames = 8192; 1115 - snd_pcm_hw_params_set_buffer_size_near(cap, hw, &buffer_frames); 1116 - 1117 1102 if (snd_pcm_hw_params(cap, hw) < 0) { 1118 1103 snprintf(audio->mic_last_error, sizeof(audio->mic_last_error), 1119 1104 "failed to configure capture"); 1120 - audio->mic_connected = 0; 1121 - audio->mic_level = 0.0f; 1122 1105 ac_log("[mic] failed to configure capture\n"); 1123 1106 snd_pcm_close(cap); 1124 - audio->mic_hot = 0; 1107 + audio->recording = 0; 1125 1108 return NULL; 1126 1109 } 1127 1110 1128 1111 audio->sample_rate = rate; 1112 + audio->sample_write_pos = 0; 1129 1113 audio->mic_connected = 1; 1130 - audio->mic_level = 0.0f; 1131 - audio->mic_last_chunk = 0; 1114 + ac_log("[mic] recording at %u Hz, %u ch\n", rate, channels); 1132 1115 1133 - // Log actual negotiated parameters 1134 - snd_pcm_uframes_t actual_period = 0, actual_buffer = 0; 1135 - snd_pcm_hw_params_get_period_size(hw, &actual_period, NULL); 1136 - snd_pcm_hw_params_get_buffer_size(hw, &actual_buffer); 1137 - ac_log("[mic] hot-mic running at %u Hz, %u ch, period=%lu buf=%lu\n", 1138 - rate, channels, (unsigned long)actual_period, (unsigned long)actual_buffer); 1139 - 1140 - // Simple blocking reads — matches original working approach. 1141 1116 int16_t buf[1024 * 2]; // max stereo 1142 - int first_read = 1; 1143 - while (audio->mic_hot) { 1117 + while (audio->recording && audio->sample_write_pos < audio->sample_max_len) { 1144 1118 int n = snd_pcm_readi(cap, buf, 512); 1145 1119 if (n < 0) { 1146 1120 n = snd_pcm_recover(cap, n, 0); 1147 - if (n < 0) { 1148 - snprintf(audio->mic_last_error, sizeof(audio->mic_last_error), 1149 - "capture read failed: %s", snd_strerror(n)); 1150 - ac_log("[mic] capture read failed: %s\n", snd_strerror(n)); 1151 - break; 1152 - } 1121 + if (n < 0) break; 1153 1122 continue; 1154 1123 } 1155 - if (first_read) { 1156 - ac_log("[mic] first capture read: %d frames\n", n); 1157 - first_read = 0; 1158 - } 1159 - audio->mic_last_chunk = n; 1160 - 1161 - // Always compute level + fill waveform ring; only buffer when recording 1162 - float peak = 0.0f; 1163 - // Downsample into waveform ring: pick evenly spaced samples from chunk 1164 - int wf_step = n > 8 ? n / 8 : 1; // ~8 samples per chunk into ring 1165 - for (int s = 0; s < n; s++) { 1124 + for (int s = 0; s < n && audio->sample_write_pos < audio->sample_max_len; s++) { 1166 1125 float sample; 1167 1126 if (channels == 1) { 1168 1127 sample = buf[s] / 32768.0f; 1169 1128 } else { 1170 1129 sample = (buf[s * 2] + buf[s * 2 + 1]) / 65536.0f; 1171 1130 } 1172 - float abs_s = fabsf(sample); 1173 - if (abs_s > peak) peak = abs_s; 1174 - 1175 - // Always write to ring buffer (recording extracts from it) 1176 - audio->mic_ring[audio->mic_ring_pos % audio->sample_max_len] = sample; 1177 - audio->mic_ring_pos++; 1178 - 1179 - // Also direct-write when recording (fast path) 1180 - if (audio->recording && audio->sample_write_pos < audio->sample_max_len) { 1181 - audio->sample_buf[audio->sample_write_pos++] = sample; 1182 - } 1183 - 1184 - // Feed waveform ring (downsampled) 1185 - if (s % wf_step == 0) { 1186 - audio->mic_waveform[audio->mic_waveform_pos % MIC_WAVEFORM_SIZE] = sample; 1187 - audio->mic_waveform_pos++; 1188 - } 1189 - } 1190 - audio->mic_level = peak; // raw peak, no smoothing 1191 - 1192 - // Cap recording at buffer limit 1193 - if (audio->recording && audio->sample_write_pos >= audio->sample_max_len) { 1194 - audio->sample_len = audio->sample_write_pos; 1195 - audio->recording = 0; 1196 - ac_log("[mic] recording buffer full (%d samples)\n", audio->sample_len); 1131 + audio->sample_buf[audio->sample_write_pos++] = sample; 1197 1132 } 1198 1133 } 1199 1134 1200 - ac_log("[mic] hot-mic thread exiting, device=%s\n", audio->mic_device); 1135 + audio->sample_len = audio->sample_write_pos; 1136 + ac_log("[mic] recorded %d samples (%.2fs)\n", 1137 + audio->sample_len, (double)audio->sample_len / audio->sample_rate); 1201 1138 snd_pcm_close(cap); 1202 1139 audio->mic_connected = 0; 1203 1140 audio->recording = 0; ··· 1205 1142 } 1206 1143 1207 1144 int audio_mic_open(ACAudio *audio) { 1208 - if (!audio || audio->mic_hot || audio->capture_thread_running) return -1; 1145 + // No-op in thread-per-recording mode — device opens on rec start 1146 + if (!audio) return -1; 1209 1147 audio->mic_hot = 1; 1210 - audio->capture_thread_running = 1; 1211 - audio->mic_last_error[0] = 0; 1212 - ac_log("[mic] opening hot-mic\n"); 1213 - if (pthread_create(&audio->capture_thread, NULL, capture_thread_func, audio) != 0) { 1214 - audio->mic_hot = 0; 1215 - audio->capture_thread_running = 0; 1216 - snprintf(audio->mic_last_error, sizeof(audio->mic_last_error), 1217 - "failed to create capture thread"); 1218 - ac_log("[mic] failed to create capture thread\n"); 1219 - return -1; 1220 - } 1148 + ac_log("[mic] mic ready (thread-per-recording mode)\n"); 1221 1149 return 0; 1222 1150 } 1223 1151 ··· 1225 1153 if (!audio) return; 1226 1154 audio->recording = 0; 1227 1155 audio->mic_hot = 0; 1228 - if (audio->capture_thread_running) { 1229 - pthread_join(audio->capture_thread, NULL); 1230 - audio->capture_thread_running = 0; 1231 - } 1232 - ac_log("[mic] hot-mic closed\n"); 1156 + ac_log("[mic] mic closed\n"); 1233 1157 } 1234 1158 1235 1159 int audio_mic_start(ACAudio *audio) { 1236 1160 if (!audio || audio->recording) return -1; 1237 - // If hot-mic isn't running yet, open it first 1238 - if (!audio->mic_hot) { 1239 - int rc = audio_mic_open(audio); 1240 - if (rc != 0) return rc; 1241 - } 1242 - // Wait for capture thread to start producing data (up to 500ms) 1243 - if (audio->mic_ring_pos == 0 && audio->mic_hot) { 1244 - for (int i = 0; i < 250 && audio->mic_ring_pos == 0; i++) { 1245 - __sync_synchronize(); 1246 - usleep(2000); // 2ms per iter, max 500ms 1247 - } 1248 - if (audio->mic_ring_pos == 0) { 1249 - ac_log("[mic] WARNING: capture thread not producing data after 500ms\n"); 1250 - } 1251 - } 1161 + audio->recording = 1; 1162 + audio->sample_len = 0; 1252 1163 // Kill any playing sample voices 1253 1164 for (int i = 0; i < AUDIO_MAX_SAMPLE_VOICES; i++) 1254 1165 audio->sample_voices[i].active = 0; 1255 - // Record ring buffer position so we can extract audio on stop 1256 - audio->rec_start_ring_pos = audio->mic_ring_pos; 1257 - // Reset write position FIRST (atomic int write), then set recording flag. 1258 - audio->sample_len = 0; 1259 - audio->sample_write_pos = 0; 1260 - __sync_synchronize(); // memory barrier: ensure pos=0 is visible before recording=1 1261 - audio->recording = 1; 1262 - ac_log("[mic] recording started (instant), ring_pos=%d\n", audio->rec_start_ring_pos); 1166 + if (pthread_create(&audio->capture_thread, NULL, capture_thread_func, audio) != 0) { 1167 + audio->recording = 0; 1168 + return -1; 1169 + } 1170 + pthread_detach(audio->capture_thread); 1171 + ac_log("[mic] recording started\n"); 1263 1172 return 0; 1264 1173 } 1265 1174 1266 1175 int audio_mic_stop(ACAudio *audio) { 1267 1176 if (!audio) return 0; 1268 1177 audio->recording = 0; 1269 - __sync_synchronize(); 1270 - 1271 - int direct_len = audio->sample_write_pos; 1272 - if (direct_len > 0) { 1273 - // Fast path: capture thread wrote directly to sample_buf 1274 - audio->sample_len = direct_len; 1275 - ac_log("[mic] recording stopped (direct), sample_len=%d sample_rate=%u\n", 1276 - audio->sample_len, audio->sample_rate); 1277 - } else { 1278 - // Fallback: extract from ring buffer (handles batched events / race) 1279 - // Spin briefly if capture thread hasn't produced data yet 1280 - int start = audio->rec_start_ring_pos; 1281 - if (audio->mic_hot) { 1282 - for (int i = 0; i < 50; i++) { 1283 - __sync_synchronize(); 1284 - if (audio->mic_ring_pos != start) break; 1285 - usleep(200); // 0.2ms per iter, max 10ms 1286 - } 1287 - } 1288 - int end = audio->mic_ring_pos; 1289 - int len = end - start; 1290 - if (len < 0) len = 0; // shouldn't happen with monotonic pos 1291 - if (len > audio->sample_max_len) len = audio->sample_max_len; 1292 - for (int i = 0; i < len; i++) { 1293 - audio->sample_buf[i] = audio->mic_ring[(start + i) % audio->sample_max_len]; 1294 - } 1295 - audio->sample_len = len; 1296 - ac_log("[mic] recording stopped (ring fallback), sample_len=%d ring_span=%d sample_rate=%u\n", 1297 - audio->sample_len, end - start, audio->sample_rate); 1298 - } 1178 + // Thread is detached and will exit on its own when it sees recording=0 1179 + usleep(50000); // 50ms for thread to finish last read 1180 + ac_log("[mic] recording stopped, sample_len=%d sample_rate=%u\n", 1181 + audio->sample_len, audio->sample_rate); 1299 1182 return audio->sample_len; 1300 1183 } 1301 1184