Monorepo for Aesthetic.Computer
aesthetic.computer
1// audio-decode.c — Streaming audio file decoder for DJ decks
2// Decodes MP3/WAV/FLAC/OGG/AAC via FFmpeg into a stereo float ring buffer.
3// Single-producer (decoder thread) / single-consumer (audio thread) lock-free design.
4
5#ifdef HAVE_AVCODEC
6
7#include "audio-decode.h"
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <math.h>
12
13#include <libavcodec/avcodec.h>
14#include <libavformat/avformat.h>
15#include <libavutil/opt.h>
16#include <libavutil/imgutils.h>
17#include <libavutil/channel_layout.h>
18#include <libswscale/swscale.h>
19#include <libswresample/swresample.h>
20
21extern void ac_log(const char *fmt, ...);
22
23// --- Internal helpers ---
24
25static void extract_metadata(ACDeckDecoder *d, AVFormatContext *fmt) {
26 const AVDictionaryEntry *tag = NULL;
27 tag = av_dict_get(fmt->metadata, "title", NULL, 0);
28 if (tag) snprintf(d->title, sizeof(d->title), "%s", tag->value);
29 tag = av_dict_get(fmt->metadata, "artist", NULL, 0);
30 if (tag) snprintf(d->artist, sizeof(d->artist), "%s", tag->value);
31}
32
33static int ring_available(ACDeckDecoder *d) {
34 return d->ring_write - d->ring_read;
35}
36
37static int ring_free(ACDeckDecoder *d) {
38 return d->ring_size - ring_available(d);
39}
40
41static void free_video_preview(ACDeckDecoder *d) {
42 if (!d) return;
43 if (d->video_frames) {
44 free(d->video_frames);
45 d->video_frames = NULL;
46 }
47 d->video_frame_count = 0;
48 d->video_width = 0;
49 d->video_height = 0;
50 d->video_fps = 0.0;
51 d->video_ready = 0;
52}
53
54// --- Decoder thread ---
55
56static void *decoder_thread_fn(void *arg) {
57 ACDeckDecoder *d = (ACDeckDecoder *)arg;
58 AVFormatContext *fmt_ctx = (AVFormatContext *)d->fmt_ctx;
59 AVCodecContext *codec_ctx = (AVCodecContext *)d->codec_ctx;
60 SwrContext *swr = (SwrContext *)d->swr;
61
62 AVPacket *pkt = av_packet_alloc();
63 AVFrame *frame = av_frame_alloc();
64 int max_out_samples = 8192;
65 float *resample_buf = NULL;
66
67 if (!pkt || !frame) {
68 d->error = 1;
69 snprintf(d->error_msg, sizeof(d->error_msg), "alloc failed");
70 goto done;
71 }
72
73 // Temporary buffer for resampled output (enough for one decoded frame)
74 resample_buf = malloc(max_out_samples * 2 * sizeof(float)); // stereo
75 if (!resample_buf) {
76 d->error = 1;
77 snprintf(d->error_msg, sizeof(d->error_msg), "resample buf alloc failed");
78 goto done;
79 }
80
81 while (d->thread_running) {
82 // Handle seek requests
83 if (d->seek_requested) {
84 int64_t ts = (int64_t)(d->seek_target * AV_TIME_BASE);
85 av_seek_frame(fmt_ctx, -1, ts, AVSEEK_FLAG_BACKWARD);
86 avcodec_flush_buffers(codec_ctx);
87 // Reset ring buffer
88 d->ring_write = 0;
89 d->ring_read = 0;
90 d->ring_frac = 0.0;
91 d->position = d->seek_target;
92 d->finished = 0;
93 d->seek_requested = 0;
94 }
95
96 // Pause: wait for signal
97 if (!d->decoding) {
98 pthread_mutex_lock(&d->mutex);
99 while (!d->decoding && d->thread_running && !d->seek_requested) {
100 pthread_cond_wait(&d->cond, &d->mutex);
101 }
102 pthread_mutex_unlock(&d->mutex);
103 continue;
104 }
105
106 // Ring buffer full: wait until consumer drains below 80%
107 if (ring_free(d) < d->ring_size / 5) {
108 pthread_mutex_lock(&d->mutex);
109 while (ring_free(d) < d->ring_size / 2 && d->thread_running && !d->seek_requested) {
110 pthread_cond_wait(&d->cond, &d->mutex);
111 }
112 pthread_mutex_unlock(&d->mutex);
113 continue;
114 }
115
116 // Read next packet
117 int ret = av_read_frame(fmt_ctx, pkt);
118 if (ret < 0) {
119 if (ret == AVERROR_EOF) {
120 d->finished = 1;
121 d->decoding = 0;
122 } else {
123 d->error = 1;
124 snprintf(d->error_msg, sizeof(d->error_msg), "read error: %d", ret);
125 }
126 continue;
127 }
128
129 if (pkt->stream_index != d->stream_idx) {
130 av_packet_unref(pkt);
131 continue;
132 }
133
134 ret = avcodec_send_packet(codec_ctx, pkt);
135 av_packet_unref(pkt);
136 if (ret < 0) continue;
137
138 while (ret >= 0) {
139 ret = avcodec_receive_frame(codec_ctx, frame);
140 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
141 if (ret < 0) { d->error = 1; break; }
142
143 // Resample: source rate -> output rate (always 1x, speed applied at playback)
144 int out_samples = av_rescale_rnd(
145 swr_get_delay(swr, d->src_sample_rate) + frame->nb_samples,
146 d->out_rate,
147 d->src_sample_rate,
148 AV_ROUND_UP
149 );
150
151 if (out_samples > max_out_samples) {
152 max_out_samples = out_samples + 1024;
153 float *newbuf = realloc(resample_buf, max_out_samples * 2 * sizeof(float));
154 if (!newbuf) { d->error = 1; break; }
155 resample_buf = newbuf;
156 }
157
158 uint8_t *out_planes[1] = { (uint8_t *)resample_buf };
159 int converted = swr_convert(swr,
160 out_planes, out_samples,
161 (const uint8_t **)frame->data, frame->nb_samples);
162
163 if (converted <= 0) continue;
164
165 // Write to ring buffer
166 int free_frames = ring_free(d);
167 int to_write = converted < free_frames ? converted : free_frames;
168
169 for (int i = 0; i < to_write; i++) {
170 int wi = (d->ring_write % d->ring_size) * 2;
171 d->ring[wi] = resample_buf[i * 2];
172 d->ring[wi + 1] = resample_buf[i * 2 + 1];
173 d->ring_write++;
174 }
175
176 // Update position based on decoded frame PTS
177 if (frame->pts != AV_NOPTS_VALUE) {
178 AVStream *st = fmt_ctx->streams[d->stream_idx];
179 d->position = frame->pts * av_q2d(st->time_base);
180 }
181
182 av_frame_unref(frame);
183 }
184 }
185
186done:
187 free(resample_buf);
188 av_packet_free(&pkt);
189 av_frame_free(&frame);
190 return NULL;
191}
192
193// --- Public API ---
194
195ACDeckDecoder *deck_decoder_create(unsigned int output_rate) {
196 ACDeckDecoder *d = calloc(1, sizeof(ACDeckDecoder));
197 if (!d) return NULL;
198
199 d->out_rate = output_rate;
200 d->speed = 1.0;
201 d->ring_frac = 0.0;
202 d->ring_size = output_rate * DECK_RING_SECONDS;
203 d->ring = calloc(d->ring_size * 2, sizeof(float)); // stereo interleaved
204 if (!d->ring) {
205 free(d);
206 return NULL;
207 }
208
209 pthread_mutex_init(&d->mutex, NULL);
210 pthread_cond_init(&d->cond, NULL);
211
212 return d;
213}
214
215int deck_decoder_load(ACDeckDecoder *d, const char *path) {
216 if (!d) return -1;
217
218 // Unload any previous file
219 deck_decoder_unload(d);
220
221 // Reset state
222 d->loaded = 0;
223 d->finished = 0;
224 d->error = 0;
225 d->error_msg[0] = '\0';
226 d->title[0] = '\0';
227 d->artist[0] = '\0';
228 d->position = 0.0;
229 d->duration = 0.0;
230 d->ring_write = 0;
231 d->ring_read = 0;
232 d->seek_requested = 0;
233 d->speed = 1.0;
234 snprintf(d->path, sizeof(d->path), "%s", path);
235 free_video_preview(d);
236
237 // Open file
238 AVFormatContext *fmt_ctx = NULL;
239 int ret = avformat_open_input(&fmt_ctx, path, NULL, NULL);
240 if (ret < 0) {
241 snprintf(d->error_msg, sizeof(d->error_msg), "cannot open: %s", path);
242 d->error = 1;
243 return -1;
244 }
245
246 ret = avformat_find_stream_info(fmt_ctx, NULL);
247 if (ret < 0) {
248 snprintf(d->error_msg, sizeof(d->error_msg), "no stream info");
249 avformat_close_input(&fmt_ctx);
250 d->error = 1;
251 return -1;
252 }
253
254 // Find best audio stream
255 int stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
256 if (stream_idx < 0) {
257 snprintf(d->error_msg, sizeof(d->error_msg), "no audio stream");
258 avformat_close_input(&fmt_ctx);
259 d->error = 1;
260 return -1;
261 }
262
263 AVStream *st = fmt_ctx->streams[stream_idx];
264 const AVCodec *codec = avcodec_find_decoder(st->codecpar->codec_id);
265 if (!codec) {
266 snprintf(d->error_msg, sizeof(d->error_msg), "unsupported codec");
267 avformat_close_input(&fmt_ctx);
268 d->error = 1;
269 return -1;
270 }
271
272 AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
273 avcodec_parameters_to_context(codec_ctx, st->codecpar);
274 ret = avcodec_open2(codec_ctx, codec, NULL);
275 if (ret < 0) {
276 snprintf(d->error_msg, sizeof(d->error_msg), "codec open failed");
277 avcodec_free_context(&codec_ctx);
278 avformat_close_input(&fmt_ctx);
279 d->error = 1;
280 return -1;
281 }
282
283 // Setup resampler: source format -> stereo float at output rate
284 SwrContext *swr = NULL;
285 AVChannelLayout out_layout = AV_CHANNEL_LAYOUT_STEREO;
286 ret = swr_alloc_set_opts2(&swr,
287 &out_layout, AV_SAMPLE_FMT_FLT, d->out_rate,
288 &codec_ctx->ch_layout, codec_ctx->sample_fmt, codec_ctx->sample_rate,
289 0, NULL);
290 if (ret < 0 || !swr) {
291 snprintf(d->error_msg, sizeof(d->error_msg), "resampler alloc failed");
292 avcodec_free_context(&codec_ctx);
293 avformat_close_input(&fmt_ctx);
294 d->error = 1;
295 return -1;
296 }
297 ret = swr_init(swr);
298 if (ret < 0) {
299 snprintf(d->error_msg, sizeof(d->error_msg), "resampler init failed");
300 swr_free(&swr);
301 avcodec_free_context(&codec_ctx);
302 avformat_close_input(&fmt_ctx);
303 d->error = 1;
304 return -1;
305 }
306
307 // Store FFmpeg state
308 d->fmt_ctx = fmt_ctx;
309 d->codec_ctx = codec_ctx;
310 d->swr = swr;
311 d->stream_idx = stream_idx;
312 d->src_sample_rate = codec_ctx->sample_rate;
313 d->src_channels = codec_ctx->ch_layout.nb_channels;
314
315 // Duration
316 if (fmt_ctx->duration > 0)
317 d->duration = (double)fmt_ctx->duration / AV_TIME_BASE;
318 else if (st->duration > 0)
319 d->duration = st->duration * av_q2d(st->time_base);
320
321 // Metadata
322 extract_metadata(d, fmt_ctx);
323
324 // Extract filename as fallback title
325 if (d->title[0] == '\0') {
326 const char *slash = strrchr(path, '/');
327 const char *name = slash ? slash + 1 : path;
328 snprintf(d->title, sizeof(d->title), "%s", name);
329 // Strip extension
330 char *dot = strrchr(d->title, '.');
331 if (dot) *dot = '\0';
332 }
333
334 d->loaded = 1;
335 d->decoding = 0; // paused until play() is called
336
337 // Start decoder thread
338 d->thread_running = 1;
339 pthread_create(&d->thread, NULL, decoder_thread_fn, d);
340
341 ac_log("[deck] loaded: %s (%s - %s, %.1fs, %dHz %dch)\n",
342 path, d->artist, d->title, d->duration,
343 d->src_sample_rate, d->src_channels);
344
345 return 0;
346}
347
348void deck_decoder_play(ACDeckDecoder *d) {
349 if (!d || !d->loaded) return;
350 if (d->finished) {
351 // Restart from beginning
352 d->seek_requested = 1;
353 d->seek_target = 0.0;
354 }
355 d->decoding = 1;
356 pthread_mutex_lock(&d->mutex);
357 pthread_cond_signal(&d->cond);
358 pthread_mutex_unlock(&d->mutex);
359}
360
361void deck_decoder_pause(ACDeckDecoder *d) {
362 if (!d) return;
363 d->decoding = 0;
364}
365
366void deck_decoder_seek(ACDeckDecoder *d, double seconds) {
367 if (!d || !d->loaded) return;
368 if (seconds < 0) seconds = 0;
369 if (seconds > d->duration) seconds = d->duration;
370 d->seek_target = seconds;
371 d->seek_requested = 1;
372 // Wake thread if paused
373 pthread_mutex_lock(&d->mutex);
374 pthread_cond_signal(&d->cond);
375 pthread_mutex_unlock(&d->mutex);
376}
377
378void deck_decoder_set_speed(ACDeckDecoder *d, double speed) {
379 if (!d) return;
380 if (speed < -4.0) speed = -4.0;
381 if (speed > 4.0) speed = 4.0;
382 d->speed = speed;
383}
384
385void deck_decoder_unload(ACDeckDecoder *d) {
386 if (!d) return;
387
388 // Stop thread
389 if (d->thread_running) {
390 d->thread_running = 0;
391 d->decoding = 0;
392 pthread_mutex_lock(&d->mutex);
393 pthread_cond_signal(&d->cond);
394 pthread_mutex_unlock(&d->mutex);
395 pthread_join(d->thread, NULL);
396 }
397
398 // Free FFmpeg state
399 if (d->swr) {
400 SwrContext *swr = (SwrContext *)d->swr;
401 swr_free(&swr);
402 d->swr = NULL;
403 }
404 if (d->codec_ctx) {
405 AVCodecContext *ctx = (AVCodecContext *)d->codec_ctx;
406 avcodec_free_context(&ctx);
407 d->codec_ctx = NULL;
408 }
409 if (d->fmt_ctx) {
410 AVFormatContext *fmt = (AVFormatContext *)d->fmt_ctx;
411 avformat_close_input(&fmt);
412 d->fmt_ctx = NULL;
413 }
414
415 // Free peaks
416 if (d->peaks) {
417 free(d->peaks);
418 d->peaks = NULL;
419 d->peak_count = 0;
420 }
421 free_video_preview(d);
422
423 d->loaded = 0;
424 d->finished = 0;
425 d->decoding = 0;
426 d->ring_write = 0;
427 d->ring_read = 0;
428}
429
430// Generate decimated max-amplitude peaks for the loaded file.
431// Opens a SECOND independent FFmpeg context (so it doesn't disturb playback)
432// and decodes the entire file once, recording max abs value per chunk.
433int deck_decoder_generate_peaks(ACDeckDecoder *d, int target_count) {
434 if (!d || !d->loaded || !d->path[0]) return -1;
435 if (d->peaks) { free(d->peaks); d->peaks = NULL; d->peak_count = 0; }
436 if (target_count <= 0) target_count = 1024;
437
438 AVFormatContext *fmt = NULL;
439 if (avformat_open_input(&fmt, d->path, NULL, NULL) < 0) return -1;
440 if (avformat_find_stream_info(fmt, NULL) < 0) {
441 avformat_close_input(&fmt);
442 return -1;
443 }
444 int sidx = av_find_best_stream(fmt, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
445 if (sidx < 0) { avformat_close_input(&fmt); return -1; }
446 AVStream *st = fmt->streams[sidx];
447 const AVCodec *codec = avcodec_find_decoder(st->codecpar->codec_id);
448 if (!codec) { avformat_close_input(&fmt); return -1; }
449 AVCodecContext *cctx = avcodec_alloc_context3(codec);
450 avcodec_parameters_to_context(cctx, st->codecpar);
451 if (avcodec_open2(cctx, codec, NULL) < 0) {
452 avcodec_free_context(&cctx);
453 avformat_close_input(&fmt);
454 return -1;
455 }
456
457 // Estimate total samples for chunking
458 double duration = d->duration;
459 if (duration <= 0) duration = 60.0; // fallback
460 int sample_rate = cctx->sample_rate;
461 long total_samples = (long)(duration * sample_rate);
462 if (total_samples < target_count) total_samples = target_count;
463 long samples_per_chunk = total_samples / target_count;
464 if (samples_per_chunk < 1) samples_per_chunk = 1;
465
466 d->peaks = (float *)calloc(target_count, sizeof(float));
467 if (!d->peaks) {
468 avcodec_free_context(&cctx);
469 avformat_close_input(&fmt);
470 return -1;
471 }
472 d->peak_count = target_count;
473
474 // Setup converter to mono float
475 SwrContext *swr = NULL;
476 AVChannelLayout out_layout = AV_CHANNEL_LAYOUT_MONO;
477 swr_alloc_set_opts2(&swr, &out_layout, AV_SAMPLE_FMT_FLT, sample_rate,
478 &cctx->ch_layout, cctx->sample_fmt, cctx->sample_rate,
479 0, NULL);
480 swr_init(swr);
481
482 AVPacket *pkt = av_packet_alloc();
483 AVFrame *frame = av_frame_alloc();
484 float chunk_max = 0.0f;
485 long sample_idx = 0;
486 int peak_idx = 0;
487 float *outbuf = (float *)malloc(sample_rate * sizeof(float));
488 int outbuf_size = sample_rate;
489
490 while (av_read_frame(fmt, pkt) >= 0 && peak_idx < target_count) {
491 if (pkt->stream_index != sidx) { av_packet_unref(pkt); continue; }
492 if (avcodec_send_packet(cctx, pkt) < 0) { av_packet_unref(pkt); continue; }
493 while (avcodec_receive_frame(cctx, frame) >= 0) {
494 int max_out = frame->nb_samples + 256;
495 if (max_out > outbuf_size) {
496 outbuf = (float *)realloc(outbuf, max_out * sizeof(float));
497 outbuf_size = max_out;
498 }
499 uint8_t *out_ptr[1] = { (uint8_t *)outbuf };
500 int out_n = swr_convert(swr, out_ptr, max_out,
501 (const uint8_t **)frame->data, frame->nb_samples);
502 for (int i = 0; i < out_n; i++) {
503 float v = outbuf[i];
504 if (v < 0) v = -v;
505 if (v > chunk_max) chunk_max = v;
506 sample_idx++;
507 if (sample_idx >= samples_per_chunk) {
508 if (peak_idx < target_count) {
509 d->peaks[peak_idx++] = chunk_max;
510 }
511 chunk_max = 0.0f;
512 sample_idx = 0;
513 }
514 }
515 }
516 av_packet_unref(pkt);
517 }
518 // Final chunk
519 if (peak_idx < target_count && chunk_max > 0) {
520 d->peaks[peak_idx++] = chunk_max;
521 }
522 // Fill remaining
523 while (peak_idx < target_count) d->peaks[peak_idx++] = 0;
524
525 free(outbuf);
526 av_frame_free(&frame);
527 av_packet_free(&pkt);
528 swr_free(&swr);
529 avcodec_free_context(&cctx);
530 avformat_close_input(&fmt);
531
532 return target_count;
533}
534
535int deck_decoder_generate_video_preview(ACDeckDecoder *d, int width, int height, int fps) {
536 if (!d || !d->loaded || !d->path[0]) return -1;
537 if (width <= 0) width = 96;
538 if (height <= 0) height = 54;
539 if (fps <= 0) fps = 12;
540 if (fps > 24) fps = 24;
541
542 // Reuse cached preview when the request matches the existing buffer.
543 if (d->video_ready &&
544 d->video_frames &&
545 d->video_width == width &&
546 d->video_height == height &&
547 fabs(d->video_fps - (double)fps) < 0.001) {
548 return d->video_frame_count;
549 }
550
551 free_video_preview(d);
552
553 AVFormatContext *fmt = NULL;
554 if (avformat_open_input(&fmt, d->path, NULL, NULL) < 0) return -1;
555 if (avformat_find_stream_info(fmt, NULL) < 0) {
556 avformat_close_input(&fmt);
557 return -1;
558 }
559
560 int sidx = av_find_best_stream(fmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
561 if (sidx < 0) {
562 avformat_close_input(&fmt);
563 return -1;
564 }
565
566 AVStream *st = fmt->streams[sidx];
567 const AVCodec *codec = avcodec_find_decoder(st->codecpar->codec_id);
568 if (!codec) {
569 avformat_close_input(&fmt);
570 return -1;
571 }
572
573 AVCodecContext *cctx = avcodec_alloc_context3(codec);
574 if (!cctx) {
575 avformat_close_input(&fmt);
576 return -1;
577 }
578 avcodec_parameters_to_context(cctx, st->codecpar);
579 if (avcodec_open2(cctx, codec, NULL) < 0) {
580 avcodec_free_context(&cctx);
581 avformat_close_input(&fmt);
582 return -1;
583 }
584
585 double duration = d->duration;
586 if (duration <= 0.0 && fmt->duration > 0)
587 duration = (double)fmt->duration / AV_TIME_BASE;
588 if (duration <= 0.0 && st->duration > 0)
589 duration = st->duration * av_q2d(st->time_base);
590 if (duration <= 0.0)
591 duration = 30.0;
592
593 int target_count = (int)ceil(duration * (double)fps) + 1;
594 if (target_count < 1) target_count = 1;
595 if (target_count > 900) target_count = 900;
596
597 size_t pixels_per_frame = (size_t)width * (size_t)height;
598 size_t total_pixels = pixels_per_frame * (size_t)target_count;
599 uint32_t *frames = (uint32_t *)calloc(total_pixels, sizeof(uint32_t));
600 if (!frames) {
601 avcodec_free_context(&cctx);
602 avformat_close_input(&fmt);
603 return -1;
604 }
605
606 struct SwsContext *sws = sws_getContext(
607 cctx->width, cctx->height, cctx->pix_fmt,
608 width, height, AV_PIX_FMT_BGRA,
609 SWS_BILINEAR, NULL, NULL, NULL);
610 if (!sws) {
611 free(frames);
612 avcodec_free_context(&cctx);
613 avformat_close_input(&fmt);
614 return -1;
615 }
616
617 AVPacket *pkt = av_packet_alloc();
618 AVFrame *frame = av_frame_alloc();
619 if (!pkt || !frame) {
620 av_packet_free(&pkt);
621 av_frame_free(&frame);
622 sws_freeContext(sws);
623 free(frames);
624 avcodec_free_context(&cctx);
625 avformat_close_input(&fmt);
626 return -1;
627 }
628
629 uint8_t *last_frame = NULL;
630 int frame_idx = 0;
631 double next_time = 0.0;
632
633 while (av_read_frame(fmt, pkt) >= 0 && frame_idx < target_count) {
634 if (pkt->stream_index != sidx) {
635 av_packet_unref(pkt);
636 continue;
637 }
638 if (avcodec_send_packet(cctx, pkt) < 0) {
639 av_packet_unref(pkt);
640 continue;
641 }
642 av_packet_unref(pkt);
643
644 while (avcodec_receive_frame(cctx, frame) >= 0 && frame_idx < target_count) {
645 double frame_time = 0.0;
646 if (frame->best_effort_timestamp != AV_NOPTS_VALUE)
647 frame_time = frame->best_effort_timestamp * av_q2d(st->time_base);
648 else if (frame->pts != AV_NOPTS_VALUE)
649 frame_time = frame->pts * av_q2d(st->time_base);
650
651 uint8_t *dst_data[4] = {0};
652 int dst_linesize[4] = {0};
653 uint32_t *dst = frames + ((size_t)frame_idx * pixels_per_frame);
654 av_image_fill_arrays(dst_data, dst_linesize, (uint8_t *)dst,
655 AV_PIX_FMT_BGRA, width, height, 1);
656
657 // Fill every preview sample point that this decoded frame covers.
658 while (frame_idx < target_count &&
659 (frame_time >= next_time || frame_idx == 0)) {
660 dst = frames + ((size_t)frame_idx * pixels_per_frame);
661 av_image_fill_arrays(dst_data, dst_linesize, (uint8_t *)dst,
662 AV_PIX_FMT_BGRA, width, height, 1);
663 sws_scale(sws, (const uint8_t * const *)frame->data, frame->linesize,
664 0, cctx->height, dst_data, dst_linesize);
665 last_frame = (uint8_t *)dst;
666 frame_idx++;
667 next_time = (double)frame_idx / (double)fps;
668 }
669
670 av_frame_unref(frame);
671 }
672 }
673
674 // Flush decoder for the tail of the file.
675 avcodec_send_packet(cctx, NULL);
676 while (avcodec_receive_frame(cctx, frame) >= 0 && frame_idx < target_count) {
677 uint8_t *dst_data[4] = {0};
678 int dst_linesize[4] = {0};
679 uint32_t *dst = frames + ((size_t)frame_idx * pixels_per_frame);
680 av_image_fill_arrays(dst_data, dst_linesize, (uint8_t *)dst,
681 AV_PIX_FMT_BGRA, width, height, 1);
682 sws_scale(sws, (const uint8_t * const *)frame->data, frame->linesize,
683 0, cctx->height, dst_data, dst_linesize);
684 last_frame = (uint8_t *)dst;
685 frame_idx++;
686 av_frame_unref(frame);
687 }
688
689 // Pad remaining preview slots with the final decoded frame so playback
690 // keeps showing a still image once audio reaches the tail.
691 if (last_frame) {
692 while (frame_idx < target_count) {
693 uint32_t *dst = frames + ((size_t)frame_idx * pixels_per_frame);
694 memcpy(dst, last_frame, pixels_per_frame * sizeof(uint32_t));
695 frame_idx++;
696 }
697 }
698
699 av_packet_free(&pkt);
700 av_frame_free(&frame);
701 sws_freeContext(sws);
702 avcodec_free_context(&cctx);
703 avformat_close_input(&fmt);
704
705 if (frame_idx <= 0) {
706 free(frames);
707 return -1;
708 }
709
710 d->video_frames = frames;
711 d->video_frame_count = frame_idx;
712 d->video_width = width;
713 d->video_height = height;
714 d->video_fps = (double)fps;
715 d->video_ready = 1;
716
717 ac_log("[deck] video preview ready: %s (%d frames @ %dx%d %.1ffps)\n",
718 d->path, d->video_frame_count, d->video_width, d->video_height, d->video_fps);
719 return d->video_frame_count;
720}
721
722void deck_decoder_destroy(ACDeckDecoder *d) {
723 if (!d) return;
724 deck_decoder_unload(d);
725 free(d->ring);
726 pthread_mutex_destroy(&d->mutex);
727 pthread_cond_destroy(&d->cond);
728 free(d);
729}
730
731#endif // HAVE_AVCODEC