FFmpeg
Loading...
Searching...
No Matches
ffplay_list.c
Go to the documentation of this file.
1/*
2 * This file is part of FFmpeg.
3 *
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * FFmpeg is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19#include "config.h"
20
21#include <limits.h>
22#include <string.h>
23
24#if CONFIG_LIBASS
25#include <ass/ass.h>
26#endif
27
28#include "libavutil/avstring.h"
29#include "libavutil/avutil.h"
30#include "libavutil/bprint.h"
31#include "libavutil/common.h"
32#include "libavutil/error.h"
33#include "libavutil/log.h"
35#include "libavutil/mem.h"
36#include "libavutil/qsort.h"
37#include "libavutil/time.h"
38
39#include "ffplay_list.h"
40
41#define LIST_FADE_TIME 250000
42#define LIST_HOLD_TIME 500000
43#define LIST_ROWS 7
44
45enum {
52};
53
54typedef struct ListSortKey {
55 int key;
58
59/* what lies under a window position: the row and the sort button, -1 for none, and whether it is inside the list at all */
60typedef struct ListHit {
61 int inside;
62 int row;
63 int button;
64 int detail;
65} ListHit;
66
67/* a strip rendered once, kept while it may be shown again */
68typedef struct ListCacheEntry {
69 char *script;
70 uint32_t background;
71 int x, w, h;
72 uint8_t *pixels;
73 unsigned last_used;
75
76/* a horizontal band of the list bitmap and the rendered strip shown there */
77typedef struct ListStrip {
78 SDL_Rect rect;
80} ListStrip;
81
82struct ListView {
83 SDL_Renderer *renderer;
84 SDL_Texture *texture;
85 SDL_Rect dirty;
86 uint8_t *bitmap;
87 SDL_Rect rect;
88#if CONFIG_LIBASS
89 ASS_Library *library;
90 ASS_Renderer *ass;
91 ASS_Track *track;
92#endif
98 unsigned frame;
101 /* whether any entry has an artist or a length, without which the column has no sort button */
103 /* the listed entries in display order, and the scratch space sorting them needs */
106 char search[64];
114 /* the search line is shown and typed into: set while pinned, kept until the list is hidden */
120 /* the columns the list is sorted by, the most recently clicked first */
124 /* 16.16 reciprocals of the alpha values, for the un-premultiplication of blended pixels */
125 uint32_t reciprocal[256];
126};
127
128static double list_alpha(const ListView *lv, int64_t now)
129{
130 double alpha = (now - lv->fade_start) / (double)LIST_FADE_TIME;
131
132 if (!lv->bitmap)
133 return 0;
134 if (!lv->pinned)
135 alpha = FFMIN(alpha, 1 - (now - lv->last_input - LIST_HOLD_TIME) / (double)LIST_FADE_TIME);
136 return av_clipd(alpha, 0, 1);
137}
138
139/* case-insensitive, with missing tags after all others */
140static int compare_tags(const char *a, const char *b)
141{
142 a = av_x_if_null(a, "");
143 b = av_x_if_null(b, "");
144 return !*a != !*b ? !*a - !*b : av_strcasecmp(a, b);
145}
146
147/* the rank of an entry in the shuffled order for the current seed */
148static uint32_t shuffle_rank(const ListView *lv, int index)
149{
150 uint32_t rank = (index + 1) * 0x9E3779B1u ^ lv->shuffle_seed * 0x85EBCA77u;
151
152 rank ^= rank >> 15;
153 rank *= 0x2C1B3C6Du;
154 return rank ^ rank >> 12;
155}
156
157static int compare_rows(const ListView *lv, const int *a, const int *b)
158{
159 const ListEntry *ea = &lv->entries[*a], *eb = &lv->entries[*b];
160
161 for (int i = 0; i < LIST_SORT_NB; i++) {
162 int cmp;
163
164 switch (lv->sort[i].key) {
165 case LIST_SORT_ARTIST: cmp = compare_tags(ea->artist, eb->artist); break;
166 case LIST_SORT_TITLE: cmp = compare_tags(ea->title, eb->title); break;
167 case LIST_SORT_LENGTH: cmp = FFDIFFSIGN(ea->length, eb->length); break;
168 case LIST_SORT_SHUFFLE: cmp = FFDIFFSIGN(shuffle_rank(lv, *a), shuffle_rank(lv, *b)); break;
169 default: cmp = *a - *b; break;
170 }
171 if (cmp)
172 return lv->sort[i].descending ? -cmp : cmp;
173 }
174 return 0;
175}
176
177/* a column clicked again flips its order, any other becomes the first key ahead of the previous ones;
178 * the shuffle button instead steps its seed by the side it was clicked on */
179static void list_sort_by(ListView *lv, int key, int seed_step)
180{
181 int i = 0;
182
183 while (lv->sort[i].key != key)
184 i++;
185 if (key == LIST_SORT_SHUFFLE)
186 lv->shuffle_seed += seed_step;
187 else if (i == 0)
188 lv->sort[0].descending ^= 1;
189 if (i == 0)
190 return;
191 memmove(&lv->sort[1], &lv->sort[0], i * sizeof(*lv->sort));
192 lv->sort[0] = (ListSortKey){ key, 0 };
193}
194
195/* rebuilds the rows matching the search in the current sort order, keeping the selected entry selected */
197{
198 int selected = list_view_selected(lv);
199
200 lv->nb_rows = 0;
201 for (int i = 0; i < lv->nb_entries; i++)
202 if (av_stristr(av_x_if_null(lv->entries[i].artist, ""), lv->search) ||
204 lv->rows[lv->nb_rows++] = i;
205#define CMP(a, b) compare_rows(lv, a, b)
206 AV_MSORT(lv->rows, lv->sort_tmp, lv->nb_rows, int, CMP);
207#undef CMP
208 lv->selected = 0;
209 for (int row = 0; row < lv->nb_rows; row++)
210 if (lv->rows[row] == selected)
211 lv->selected = row;
212}
213
214static const struct {
215 const char *label;
216 int width;
218 [LIST_SORT_NUMBER] = { "#", 5 },
219 [LIST_SORT_ARTIST] = { "Artist", 8 },
220 [LIST_SORT_TITLE] = { "Title", 7 },
221 [LIST_SORT_LENGTH] = { "Duration", 10 },
222 [LIST_SORT_SHUFFLE] = { "Shuffle", 14 },
224
225#define LIST_EVENT "Dialogue: 0:00:00.00,9999:00:00.00,"
226
227static void bprint_box(AVBPrint *script, const char *style, int x, int y, int w, int h)
228{
229 av_bprintf(script, LIST_EVENT "%s,{\\pos(%d,%d)\\p1}m 0 0 l %d 0 %d %d 0 %d{\\p0}\n", style, x, y, w, w, h, h);
230}
231
232/* a text cell cut off at the right and bottom of its column, with the characters libass would read as tags or line breaks blanked */
233static void bprint_cell(AVBPrint *script, const char *style, int x, int y, int right, int bottom, const char *text)
234{
235 av_bprintf(script, LIST_EVENT "%s,{\\pos(%d,%d)\\clip(0,0,%d,%d)}", style, x, y, right, bottom);
236 for (text = av_x_if_null(text, ""); *text; text++)
237 av_bprint_chars(script, strchr("{}\\\r\n", *text) ? ' ' : *text, 1);
238 av_bprint_chars(script, '\n', 1);
239}
240
246
247/* button widths are in half font sizes, a quarter apart, wrapping onto further lines when the panel is too narrow */
249{
250 ListLayout l;
251 int x, y;
252
253 l.font = FFMAX(lv->height / 30, 12);
254 l.line = l.font * 9 / 4;
255 l.width = FFMIN(FFMAX(lv->width * 3 / 5, l.font * 26), lv->width - l.font * 2);
256 x = y = l.font / 2;
257 for (int b = 0; b < LIST_SORT_NB; b++) {
258 int w = sort_buttons[b].width * l.font / 2;
259
260 if (b == LIST_SORT_ARTIST && !lv->has_artist || b == LIST_SORT_LENGTH && !lv->has_length) {
261 l.button_x[b] = -1;
262 l.button_y[b] = 0;
263 continue;
264 }
265 if (x > l.font / 2 && x + w > l.width - l.font / 2) {
266 x = l.font / 2;
267 y += l.font * 3 / 2;
268 }
269 l.button_x[b] = x;
270 l.button_y[b] = y;
271 x += w + l.font / 4;
272 }
273 /* the search field follows the buttons, or takes its own line when they leave it less than 8 em */
274 if (x + l.font * 8 > l.width - l.font / 2) {
275 x = l.font / 2;
276 y += l.font * 3 / 2;
277 }
278 l.search_x = x;
279 l.search_y = y;
280 l.rows_y = lv->typing ? y + l.font * 3 / 2 : l.button_y[LIST_SORT_NB - 1] + l.font * 3 / 2;
281 l.canvas_h = FFMIN(y + l.font * 2 + LIST_ROWS * l.line, lv->height - l.font * 2);
282 l.nb_rows = FFMIN(lv->nb_rows, FFMAX(1, (l.canvas_h - l.rows_y - l.font / 2) / l.line));
283 l.first = av_clip(lv->selected - l.nb_rows / 2, 0, lv->nb_rows - l.nb_rows);
284 l.height = l.rows_y + l.nb_rows * l.line + l.font / 2;
285 l.text_x = l.font * 3;
286 l.text_right = l.width - l.font * 11 / 2;
287 l.length_x = l.width - l.font * 5 / 2;
288 l.detail_x = l.width - l.font * 5 / 4;
289 l.canvas_w = lv->width - l.font * 2;
290 return l;
291}
292
293static ListHit list_hit(const ListLayout *l, int x, int y)
294{
295 ListHit hit = { 0, -1, -1, 0 };
296 int line;
297
298 x -= l->font;
299 y -= l->font;
300 if (x < 0 || y < 0 || x >= l->width || y >= FFMIN(l->height, l->canvas_h))
301 return hit;
302 hit.inside = 1;
303 line = (y - l->rows_y) / l->line;
304 if (y >= l->rows_y && line < l->nb_rows) {
305 hit.row = l->first + line;
306 hit.detail = x >= l->width - l->font * 2;
307 }
308 for (int b = 0; b < FF_ARRAY_ELEMS(sort_buttons); b++)
309 if (l->button_x[b] >= 0 && x >= l->button_x[b] && x < l->button_x[b] + sort_buttons[b].width * l->font / 2 &&
310 y >= l->button_y[b] && y < l->button_y[b] + l->font * 5 / 4)
311 hit.button = b;
312 return hit;
313}
314
315/* a circle outline drawn as four bezier arcs around a center */
316static void bprint_ring(AVBPrint *script, int cx, int cy, int r)
317{
318 int k = r * 552 / 1000;
319
320 av_bprintf(script, LIST_EVENT "Ring,{\\an5\\pos(%d,%d)\\p1}m 0 %d b 0 %d %d 0 %d 0 b %d 0 %d %d %d %d "
321 "b %d %d %d %d %d %d b %d %d 0 %d 0 %d{\\p0}\n",
322 cx, cy, r, r - k, r - k, r, r + k, 2 * r, r - k, 2 * r, r, 2 * r, r + k, r + k, 2 * r, r, 2 * r,
323 r - k, 2 * r, r + k, r);
324}
325
326/* strip backgrounds as ARGB */
327#define PANEL_COLOR 0xBF000000
328#define HOVER_COLOR 0xCD444444
329#define POPUP_COLOR 0xDF000000
330
331static void bprint_styles(AVBPrint *script, const ListLayout *l)
332{
333 av_bprintf(script,
334 "[Script Info]\nScriptType: v4.00+\nPlayResX: %d\nPlayResY: %d\nWrapStyle: 2\nYCbCr Matrix: None\n\n"
335 "[V4+ Styles]\n"
336 "Format: Name, Fontname, Fontsize, PrimaryColour, OutlineColour, Bold, Outline, Alignment\n"
337 "Style: Button,Sans,%d,&H80FFFFFF,&H80FFFFFF,0,0,7\n"
338 "Style: Row,Sans,%d,&H00FFFFFF,&H00000000,0,%d,7\n"
339 "Style: Selected,Sans,%d,&H0080D0FF,&H00000000,-1,%d,7\n"
340 "Style: Artist,Sans,%d,&H00C0C0C0,&H00000000,0,%d,7\n"
341 "Style: ArtistSelected,Sans,%d,&H0080D0FF,&H00000000,-1,%d,7\n"
342 "Style: Playing,Sans,%d,&H00FFFFFF,&H00FFFFFF,0,0,7\n"
343 "Style: Hint,Sans,%d,&H00A0A0A0,&H00000000,0,%d,7\n"
344 "Style: ButtonHover,Sans,%d,&H50FFFFFF,&H50FFFFFF,0,0,7\n"
345 "Style: Ring,Sans,%d,&HFF000000,&H00A0A0A0,0,%d,7\n"
346 "Style: Detail,Sans,%d,&H00FFFFFF,&H00000000,0,%d,7\n\n"
347 "[Events]\nFormat: Start, End, Style, Text\n",
348 l->canvas_w, l->canvas_h, l->font, l->font, l->font / 16 + 1, l->font, l->font / 16 + 1,
349 l->font * 4 / 5, l->font / 16 + 1, l->font * 4 / 5, l->font / 16 + 1, l->font, l->font, l->font / 16 + 1,
350 l->font, l->font, l->font / 16 + 1, l->font * 4 / 5, l->font / 16 + 1);
351}
352
353static void bprint_header_strip(const ListView *lv, AVBPrint *script, const ListLayout *l)
354{
355 if (lv->typing)
356 bprint_cell(script, *lv->search ? "Row" : "Hint", l->search_x, l->search_y, l->length_x,
357 l->search_y + l->font * 3 / 2, *lv->search ? lv->search : "type to search");
358 for (int b = 0; b < FF_ARRAY_ELEMS(sort_buttons); b++) {
359 int x = l->button_x[b], y = l->button_y[b], w = sort_buttons[b].width * l->font / 2;
360
361 if (x < 0)
362 continue;
363 bprint_box(script, b == lv->hover.button ? "ButtonHover" : "Button", x, y, w, l->font * 5 / 4);
364 av_bprintf(script, LIST_EVENT "%s,{\\an5\\pos(%d,%d)}", lv->sort[0].key == b ? "Selected" : "Row",
365 x + w / 2, y + l->font * 5 / 8);
366 if (b == LIST_SORT_SHUFFLE)
367 av_bprintf(script, "\xe2\x97\x82 %s %d \xe2\x96\xb8\n", sort_buttons[b].label, lv->shuffle_seed);
368 else
369 av_bprintf(script, "%s%s\n", sort_buttons[b].label,
370 lv->sort[0].key != b ? "" : lv->sort[0].descending ? " \xe2\x96\xbc" : " \xe2\x96\xb2");
371 }
372}
373
374static void bprint_row_strip(const ListView *lv, AVBPrint *script, const ListLayout *l, int r)
375{
376 int index = lv->rows[r];
377 const ListEntry *entry = &lv->entries[index];
378 const char *style = r == lv->selected ? "Selected" : "Row";
379 int64_t seconds;
380
381 if (index == lv->playing)
382 bprint_box(script, "Playing", 0, 0, l->font / 6, l->line);
383 av_bprintf(script, LIST_EVENT "%s,{\\an6\\pos(%d,%d)}%d\n", style, l->font * 5 / 2, l->line / 2, index + 1);
384 bprint_cell(script, style, l->text_x, l->font / 8, l->text_right, l->line, entry->title);
385 bprint_cell(script, r == lv->selected ? "ArtistSelected" : "Artist", l->text_x, l->font * 5 / 4,
386 l->text_right, l->line, entry->artist);
387 bprint_ring(script, l->detail_x, l->line / 2, l->font * 9 / 20);
388 av_bprintf(script, LIST_EVENT "Hint,{\\an5\\pos(%d,%d)}i\n", l->detail_x, l->line / 2);
389 if (entry->length < 0)
390 return;
391 seconds = av_rescale_rnd(entry->length, 1, AV_TIME_BASE, AV_ROUND_NEAR_INF);
392 av_bprintf(script, LIST_EVENT "%s,{\\an6\\pos(%d,%d)}", style, l->length_x, l->line / 2);
393 if (seconds >= 3600)
394 av_bprintf(script, "%"PRId64":%02d:%02d\n", seconds / 3600, (int)(seconds / 60 % 60), (int)(seconds % 60));
395 else
396 av_bprintf(script, "%d:%02d\n", (int)(seconds / 60), (int)(seconds % 60));
397}
398
400{
401 if (*entry) {
402 av_freep(&(*entry)->script);
403 av_freep(&(*entry)->pixels);
404 }
406}
407
408/* drops the least recently used strips not shown in this frame until the new one fits into twice the bitmap size */
409static void cache_make_room(ListView *lv, size_t bytes)
410{
411 while (lv->cache_bytes + bytes > 2 * (size_t)lv->rect.w * lv->rect.h * 4) {
412 int oldest = -1;
413
414 for (int i = 0; i < lv->nb_cache; i++)
415 if (lv->cache[i]->last_used != lv->frame && (oldest < 0 || lv->cache[i]->last_used < lv->cache[oldest]->last_used))
416 oldest = i;
417 if (oldest < 0)
418 return;
419 lv->cache_bytes -= (size_t)lv->cache[oldest]->w * lv->cache[oldest]->h * 4;
420 cache_entry_free(&lv->cache[oldest]);
421 lv->cache[oldest] = lv->cache[--lv->nb_cache];
422 }
423}
424
425static int strip_render(ListView *lv, ListCacheEntry *entry);
426
427/* the cached strip for the script, background and place, rendered when there is none */
428static int cache_get(ListView *lv, const SDL_Rect *rect, uint32_t background, const AVBPrint *script, ListCacheEntry **entry)
429{
430 int ret;
431
432 for (int i = 0; i < lv->nb_cache; i++) {
433 ListCacheEntry *e = lv->cache[i];
434
435 if (e->background == background && e->x == rect->x && e->w == rect->w && e->h == rect->h &&
436 !strcmp(e->script, script->str)) {
437 e->last_used = lv->frame;
438 *entry = e;
439 return 0;
440 }
441 }
442 cache_make_room(lv, (size_t)rect->w * rect->h * 4);
443 if (!(*entry = av_mallocz(sizeof(**entry))))
444 return AVERROR(ENOMEM);
445 **entry = (ListCacheEntry){ av_strdup(script->str), background, rect->x, rect->w, rect->h,
446 av_malloc_array(rect->w * rect->h, 4), lv->frame };
447 ret = (*entry)->script && (*entry)->pixels ? strip_render(lv, *entry) : AVERROR(ENOMEM);
448 if (ret >= 0)
450 if (ret < 0) {
452 return ret;
453 }
454 lv->cache_bytes += (size_t)rect->w * rect->h * 4;
455 return 0;
456}
457
458static int strip_add(ListView *lv, ListStrip **strips, int *nb, SDL_Rect rect, uint32_t background, const AVBPrint *script)
459{
461 ListStrip *strip;
462 int ret;
463
464 if (rect.w <= 0 || rect.h <= 0)
465 return 0;
466 if (!av_bprint_is_complete(script))
467 return AVERROR(ENOMEM);
468 if ((ret = cache_get(lv, &rect, background, script, &entry)) < 0)
469 return ret;
470 if (!(strip = av_dynarray2_add((void **)strips, nb, sizeof(*strip), NULL)))
471 return AVERROR(ENOMEM);
472 *strip = (ListStrip){ rect, entry };
473 return 0;
474}
475
476/* the strips the list consists of right now: the header, each visible row, the bottom padding and the metadata popup,
477 * beside the list or over its rows when the window leaves no room there */
478static int list_strips(ListView *lv, const ListLayout *l, ListStrip **strips, int *nb)
479{
480 AVBPrint script;
481 int ret = 0;
482
484 for (int i = 0; ret >= 0 && i < l->nb_rows + 2; i++) {
485 int row = i && i <= l->nb_rows ? l->first + i - 1 : -1;
486 SDL_Rect rect = { 0, i ? l->rows_y + (i - 1) * l->line : 0, l->width, i ? l->line : l->rows_y };
487
488 if (i > l->nb_rows)
489 rect.h = l->font / 2;
490 rect.h = FFMIN(rect.h, l->canvas_h - rect.y);
491 av_bprint_clear(&script);
492 if (!i)
493 bprint_header_strip(lv, &script, l);
494 else if (row >= 0)
495 bprint_row_strip(lv, &script, l, row);
496 ret = strip_add(lv, strips, nb, rect, row >= 0 && row == lv->hover.row ? HOVER_COLOR : PANEL_COLOR, &script);
497 }
498 if (ret >= 0 && lv->hover.detail) {
499 const AVDictionary *metadata = lv->entries[lv->rows[lv->hover.row]].metadata;
500 const AVDictionaryEntry *tag = NULL;
501 int beside = l->canvas_w - l->width >= l->font * 12;
502 int height = FFMIN(av_dict_count(metadata) * l->font + l->font / 2, l->canvas_h);
503 int x = beside ? l->width + l->font / 2 : l->text_x;
504 int right = beside ? l->canvas_w : l->length_x - l->font / 2;
505 SDL_Rect rect = { x, av_clip(l->rows_y + (lv->hover.row - l->first) * l->line, 0, l->canvas_h - height),
506 right - x, height };
507
508 av_bprint_clear(&script);
509 for (int top = l->font / 4; top < height + l->font && (tag = av_dict_iterate(metadata, tag)); top += l->font) {
510 bprint_cell(&script, "Detail", x + l->font / 2, top, x + l->font * 7, height, tag->key);
511 bprint_cell(&script, "Detail", x + l->font * 15 / 2, top, right - l->font / 4, height, tag->value);
512 }
513 ret = strip_add(lv, strips, nb, rect, POPUP_COLOR, &script);
514 }
515 av_bprint_finalize(&script, NULL);
516 return ret;
517}
518
519static void list_discard(ListView *lv)
520{
521 av_freep(&lv->bitmap);
522 av_freep(&lv->strips);
523 lv->nb_strips = 0;
524 for (int i = 0; i < lv->nb_cache; i++)
525 cache_entry_free(&lv->cache[i]);
526 av_freep(&lv->cache);
527 lv->nb_cache = 0;
528 lv->cache_bytes = 0;
529 if (lv->texture)
530 SDL_DestroyTexture(lv->texture);
531 lv->texture = NULL;
532#if CONFIG_LIBASS
533 if (lv->track)
534 ass_free_track(lv->track);
535 lv->track = NULL;
536#endif
537}
538
539#if CONFIG_LIBASS
540static void libass_log(int libass_level, const char *fmt, va_list args, void *opaque)
541{
542 static const int levels[] = { AV_LOG_FATAL, AV_LOG_ERROR, AV_LOG_WARNING, AV_LOG_WARNING,
544 int level = levels[av_clip(libass_level, 0, FF_ARRAY_ELEMS(levels) - 1)];
545
546 av_vlog(NULL, level, fmt, args);
547 av_log(NULL, level, "\n");
548}
549#endif
550
551/* the bitmap, the texture it is uploaded to, and libass with the styles of the layout, rendering a strip at a time */
552static int list_configure(ListView *lv, const ListLayout *l, const SDL_Rect *canvas)
553{
554#if CONFIG_LIBASS
555 AVBPrint styles;
556
557 list_discard(lv);
558 lv->rect = *canvas;
559 if (l->canvas_w <= 0 || l->canvas_h <= 0 || l->canvas_w > INT_MAX / 4 / l->canvas_h)
560 return AVERROR(EINVAL);
561 if (!(lv->bitmap = av_mallocz(l->canvas_w * l->canvas_h * 4)))
562 return AVERROR(ENOMEM);
563 if (!(lv->texture = SDL_CreateTexture(lv->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING,
564 l->canvas_w, l->canvas_h)) ||
565 SDL_SetTextureBlendMode(lv->texture, SDL_BLENDMODE_BLEND) < 0)
566 return AVERROR_EXTERNAL;
567 lv->dirty = (SDL_Rect){ 0, 0, l->canvas_w, l->canvas_h };
568 if (!lv->library) {
569 if (!(lv->library = ass_library_init()))
570 return AVERROR(ENOMEM);
571 ass_set_message_cb(lv->library, libass_log, NULL);
572 ass_set_extract_fonts(lv->library, 1);
573 if (!(lv->ass = ass_renderer_init(lv->library))) {
574 ass_library_done(lv->library);
575 lv->library = NULL;
576 return AVERROR(ENOMEM);
577 }
578 ass_set_fonts(lv->ass, NULL, NULL, 1, NULL, 1);
579 }
581 bprint_styles(&styles, l);
582 if (av_bprint_is_complete(&styles) && (lv->track = ass_new_track(lv->library)))
583 ass_process_codec_private(lv->track, styles.str, styles.len);
584 av_bprint_finalize(&styles, NULL);
585 if (!lv->track)
586 return AVERROR(ENOMEM);
587 ass_set_frame_size(lv->ass, l->canvas_w, l->canvas_h);
588 ass_set_storage_size(lv->ass, l->canvas_w, l->canvas_h);
589 return 0;
590#else
591 if (!lv->libass_missing_reported++)
592 av_log(NULL, AV_LOG_ERROR, "The list needs libass, which this build lacks\n");
593 return AVERROR(ENOSYS);
594#endif
595}
596
597#if CONFIG_LIBASS
598/* the color with opacity a over the pixel, both ARGB, using the 16.16 reciprocals of the alpha values */
599static uint32_t blend_over(uint32_t dst, uint32_t rgb, int a, const uint32_t *reciprocal)
600{
601 int dst_a = dst >> 24, out_a = a + (dst_a * (255 - a) + 127) / 255;
602 uint32_t out = (uint32_t)out_a << 24;
603
604 for (int shift = 0; shift < 24; shift += 8) {
605 int premultiplied = ((rgb >> shift & 0xFF) * a * 255 + (dst >> shift & 0xFF) * dst_a * (255 - a) + 255 * 127) / (255 * 255);
606
607 out |= FFMIN(premultiplied * reciprocal[out_a] >> 16, 255) << shift;
608 }
609 return out;
610}
611#endif
612
613/* renders the strip over its background */
615{
616#if CONFIG_LIBASS
617 uint32_t *pixels = (uint32_t *)entry->pixels;
618 int w = entry->w, h = entry->h;
619
620 ass_flush_events(lv->track);
621 ass_process_data(lv->track, entry->script, strlen(entry->script));
622 for (int i = 0; i < w * h; i++)
623 pixels[i] = entry->background;
624 for (const ASS_Image *image = ass_render_frame(lv->ass, lv->track, 0, NULL); image; image = image->next) {
625 int opacity = 255 - (image->color & 0xFF);
626 int x0 = FFMAX(image->dst_x, entry->x), x1 = FFMIN(image->dst_x + image->w, entry->x + w);
627
628 for (int y = FFMAX(image->dst_y, 0); x0 < x1 && y < FFMIN(image->dst_y + image->h, h); y++) {
629 const uint8_t *coverage = image->bitmap + (y - image->dst_y) * image->stride + x0 - image->dst_x;
630 uint32_t *dst = pixels + y * w + x0 - entry->x;
631
632 for (int x = 0; x < x1 - x0; x++) {
633 int a = (opacity * coverage[x] + 127) / 255;
634
635 if (a)
636 dst[x] = blend_over(dst[x], image->color >> 8, a, lv->reciprocal);
637 }
638 }
639 }
640 return 0;
641#else
642 return AVERROR(ENOSYS);
643#endif
644}
645
646/* brings the bitmap up to date: what differs from the strips shown before is cleared and composed again from the strips
647 * covering it, rendering only strips that are not cached */
648static int list_render(ListView *lv)
649{
650 ListLayout l = list_layout(lv);
651 SDL_Rect canvas = { l.font, l.font, l.canvas_w, l.canvas_h }, dirty = { 0, 0, 0, 0 };
652 ListStrip *strips = NULL;
653 int nb = 0, ret = 0;
654
655 lv->render_pending = 0;
656 lv->hover = list_hit(&l, lv->mouse_x, lv->mouse_y);
657 /* the strips shown stay cached until compared with the new ones */
658 lv->frame++;
659 for (int i = 0; i < lv->nb_strips; i++)
660 lv->strips[i].entry->last_used = lv->frame;
661 if (!lv->bitmap || !SDL_RectEquals(&lv->rect, &canvas))
662 ret = list_configure(lv, &l, &canvas);
663 if (ret >= 0)
664 ret = list_strips(lv, &l, &strips, &nb);
665 for (int i = 0; ret >= 0 && i < FFMAX(nb, lv->nb_strips); i++) {
666 const ListStrip *old = i < lv->nb_strips ? &lv->strips[i] : NULL, *new = i < nb ? &strips[i] : NULL;
667
668 if (old && new && old->entry == new->entry && SDL_RectEquals(&old->rect, &new->rect))
669 continue;
670 if (old)
671 SDL_UnionRect(&dirty, &old->rect, &dirty);
672 if (new)
673 SDL_UnionRect(&dirty, &new->rect, &dirty);
674 }
675 if (ret >= 0 && dirty.w) {
676 for (int y = dirty.y; y < dirty.y + dirty.h; y++)
677 memset(lv->bitmap + (y * lv->rect.w + dirty.x) * 4, 0, dirty.w * 4);
678 for (int i = 0; i < nb; i++) {
679 const ListCacheEntry *entry = strips[i].entry;
680 SDL_Rect part;
681
682 if (!SDL_IntersectRect(&strips[i].rect, &dirty, &part))
683 continue;
684 for (int y = part.y; y < part.y + part.h; y++)
685 memcpy(lv->bitmap + (y * lv->rect.w + part.x) * 4,
686 entry->pixels + ((y - strips[i].rect.y) * entry->w + part.x - strips[i].rect.x) * 4, part.w * 4);
687 }
688 SDL_UnionRect(&lv->dirty, &dirty, &lv->dirty);
689 lv->changed = 1;
690 }
691 if (ret < 0) {
692 if (ret != AVERROR(ENOSYS))
693 av_log(NULL, AV_LOG_ERROR, "Could not render the list: %s\n", av_err2str(ret));
694 list_discard(lv);
695 av_freep(&strips);
696 nb = 0;
697 if (lv->pinned)
698 SDL_StopTextInput();
699 lv->pinned = 0;
700 }
701 av_freep(&lv->strips);
702 lv->strips = strips;
703 lv->nb_strips = nb;
704 return ret;
705}
706
707static void list_select(ListView *lv, int row)
708{
710
711 lv->selected = row;
712 lv->fade_start = now - list_alpha(lv, now) * LIST_FADE_TIME;
713 lv->last_input = now;
714 lv->render_pending = 1;
715}
716
717void list_view_text(ListView *lv, const char *text)
718{
719 int len = strlen(lv->search);
720
721 if (text) {
722 if (len + strlen(text) < sizeof(lv->search))
723 av_strlcat(lv->search, text, sizeof(lv->search));
724 } else if (len) {
725 while (len > 0 && (lv->search[--len] & 0xC0) == 0x80)
726 ;
727 lv->search[len] = 0;
728 }
730 lv->render_pending = 1;
731}
732
734{
735 ListView *lv = av_mallocz(sizeof(*lv));
736
737 if (!lv)
738 return NULL;
739 lv->renderer = renderer;
740 lv->playing = -1;
741 lv->mouse_x = lv->mouse_y = -1;
742 lv->hover = (ListHit){ 0, -1, -1, 0 };
743 lv->reciprocal[0] = 1 << 16;
744 for (int a = 1; a < 256; a++)
745 lv->reciprocal[a] = ((255 << 16) + a - 1) / a;
746 list_view_clear(lv);
747 return lv;
748}
749
751{
752 ListView *lv = *plv;
753
754 if (!lv)
755 return;
756 list_discard(lv);
757#if CONFIG_LIBASS
758 if (lv->ass)
759 ass_renderer_done(lv->ass);
760 if (lv->library)
761 ass_library_done(lv->library);
762#endif
763 av_freep(&lv->entries);
764 av_freep(plv);
765}
766
767int list_view_show(ListView *lv, const ListEntry *entries, int nb_entries, int selected)
768{
770 int previous = list_view_selected(lv);
771
772 if (!lv->renderer)
773 return AVERROR(EINVAL);
774 /* the rows and the sort scratch follow the entries */
775 copy = av_malloc_array(nb_entries, sizeof(*copy) + 2 * sizeof(*lv->rows));
776 if (!copy)
777 return AVERROR(ENOMEM);
778 if (nb_entries)
779 memcpy(copy, entries, nb_entries * sizeof(*entries));
780 av_free(lv->entries);
781 lv->entries = copy;
782 lv->rows = (int *)(copy + nb_entries);
783 lv->sort_tmp = lv->rows + nb_entries;
784 lv->nb_entries = nb_entries;
785 lv->nb_rows = 0;
786 lv->has_artist = lv->has_length = 0;
787 for (int i = 0; i < nb_entries; i++) {
788 lv->has_artist |= entries[i].artist && *entries[i].artist;
789 lv->has_length |= entries[i].length >= 0;
790 }
792 for (int row = 0; row < lv->nb_rows; row++)
793 if (lv->rows[row] == previous)
794 lv->selected = row;
795 for (int row = 0; row < lv->nb_rows; row++)
796 if (lv->rows[row] == selected)
797 lv->selected = row;
798 list_select(lv, lv->selected);
799 return 0;
800}
801
803{
804 lv->nb_entries = lv->nb_rows = lv->selected = 0;
805 lv->search[0] = 0;
806 for (int i = 0; i < LIST_SORT_NB; i++)
807 lv->sort[i] = (ListSortKey){ i, 0 };
808 lv->shuffle_seed = 0;
809 list_discard(lv);
810}
811
812void list_view_pin(ListView *lv, int pinned)
813{
814 lv->pinned = pinned;
815 if (pinned) {
816 SDL_StartTextInput();
817 lv->typing = 1;
818 } else {
819 SDL_StopTextInput();
821 }
822 lv->render_pending = 1;
823}
824
826{
827 return lv->pinned;
828}
829
831{
832 return lv->pinned || list_alpha(lv, av_gettime_relative()) > 0;
833}
834
836{
837 return lv->nb_rows ? lv->rows[lv->selected] : -1;
838}
839
841{
842 if (lv->nb_rows)
843 list_select(lv, av_clip(lv->selected + delta, 0, lv->nb_rows - 1));
844}
845
846int list_view_key(ListView *lv, SDL_Keycode key)
847{
848 switch (key) {
849 case SDLK_ESCAPE: list_view_pin(lv, 0); break;
850 case SDLK_UP: list_view_move(lv, -1); break;
851 case SDLK_DOWN: list_view_move(lv, 1); break;
852 case SDLK_BACKSPACE: list_view_text(lv, NULL); break;
853 case SDLK_RETURN:
854 case SDLK_KP_ENTER: return list_view_selected(lv);
855 }
856 return -1;
857}
858
860{
861 lv->search[0] = 0;
863 lv->render_pending = 1;
864}
865
866/* asks for a render when what a visible list points at changed; the render waits until the pending events are handled,
867 * so a sweep across the list renders once */
868void list_view_hover(ListView *lv, int x, int y)
869{
870 ListLayout l;
871 ListHit hit;
872
873 lv->mouse_x = x;
874 lv->mouse_y = y;
875 if (!list_view_visible(lv))
876 return;
877 l = list_layout(lv);
878 hit = list_hit(&l, x, y);
879 if (hit.row != lv->hover.row || hit.button != lv->hover.button || hit.detail != lv->hover.detail)
880 lv->render_pending = 1;
881}
882
883int list_view_click(ListView *lv, int x, int y, int *chosen)
884{
885 ListLayout l = list_layout(lv);
886 ListHit hit = list_hit(&l, x, y);
887
888 *chosen = hit.row >= 0 && !hit.detail ? lv->rows[hit.row] : -1;
889 if (hit.button >= 0) {
890 int button_w = sort_buttons[hit.button].width * l.font / 2;
891
892 list_sort_by(lv, hit.button, (x - l.font - l.button_x[hit.button]) * 2 < button_w ? -1 : 1);
894 list_select(lv, lv->selected);
895 }
896 return hit.inside;
897}
898
900{
901 if (lv->width == width && lv->height == height)
902 return;
903 lv->width = width;
904 lv->height = height;
905 if (list_view_visible(lv))
906 lv->render_pending = 1;
907 else
908 list_discard(lv);
909}
910
912{
913 if (lv->playing == entry)
914 return;
915 lv->playing = entry;
916 lv->render_pending = 1;
917}
918
920{
921 double alpha = list_alpha(lv, av_gettime_relative());
922 int changed;
923
924 if (lv->typing && !lv->pinned && alpha <= 0) {
925 lv->typing = 0;
926 lv->search[0] = 0;
928 }
929 if (lv->render_pending && lv->width)
930 list_render(lv);
931 changed = lv->changed || alpha != lv->drawn_alpha;
932 lv->changed = 0;
933 return changed;
934}
935
936/* uploads what the texture lacks of the bitmap, at most once per refresh */
938{
939 double alpha = list_alpha(lv, av_gettime_relative());
940 SDL_Rect *dirty = &lv->dirty;
941
942 lv->drawn_alpha = alpha;
943 if (alpha <= 0)
944 return;
945 if (dirty->w && !SDL_UpdateTexture(lv->texture, dirty, lv->bitmap + (dirty->y * lv->rect.w + dirty->x) * 4, lv->rect.w * 4))
946 *dirty = (SDL_Rect){ 0, 0, 0, 0 };
947 SDL_SetTextureAlphaMod(lv->texture, alpha * 255);
948 SDL_RenderCopy(lv->renderer, lv->texture, NULL, &lv->rect);
949}
uint8_t ptrdiff_t const uint8_t ptrdiff_t int intptr_t intptr_t int int16_t * dst
Definition dsp.h:97
#define entry
static FILE * out
Convenience header that includes libavutil's core.
void av_bprintf(AVBPrint *buf, const char *fmt,...)
Definition bprint.c:121
void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max)
Definition bprint.c:68
AVBPrint public header.
#define AV_BPRINT_SIZE_UNLIMITED
static int FUNC metadata(CodedBitstreamContext *ctx, RWContext *rw, APVRawMetadata *current)
#define i(width, name, range_min, range_max)
Definition cbs_h264.c:63
common internal and external API header
#define av_clipd
Definition common.h:148
#define av_clip
Definition common.h:100
#define NULL
Definition coverity.c:32
long long int64_t
Definition coverity.c:34
error code definitions
const char * key
int ret
Definition ffplay.c:391
static SDL_Renderer * renderer
Definition ffplay.c:422
int list_view_selected(const ListView *lv)
void list_view_set_size(ListView *lv, int width, int height)
Sets the size of the window the list is drawn into.
#define POPUP_COLOR
static int list_render(ListView *lv)
static int list_strips(ListView *lv, const ListLayout *l, ListStrip **strips, int *nb)
static double list_alpha(const ListView *lv, int64_t now)
static int cache_get(ListView *lv, const SDL_Rect *rect, uint32_t background, const AVBPrint *script, ListCacheEntry **entry)
ListView * list_view_alloc(SDL_Renderer *renderer)
static int compare_tags(const char *a, const char *b)
void list_view_set_playing(ListView *lv, int entry)
Marks the entry as playing, -1 for none.
static void bprint_cell(AVBPrint *script, const char *style, int x, int y, int right, int bottom, const char *text)
static void bprint_row_strip(const ListView *lv, AVBPrint *script, const ListLayout *l, int r)
static void bprint_header_strip(const ListView *lv, AVBPrint *script, const ListLayout *l)
static void bprint_box(AVBPrint *script, const char *style, int x, int y, int w, int h)
static int list_configure(ListView *lv, const ListLayout *l, const SDL_Rect *canvas)
static void list_discard(ListView *lv)
static uint32_t shuffle_rank(const ListView *lv, int index)
int list_view_show(ListView *lv, const ListEntry *entries, int nb_entries, int selected)
Replaces the entries and shows the list, fading it in.
static ListLayout list_layout(const ListView *lv)
static int compare_rows(const ListView *lv, const int *a, const int *b)
int list_view_pinned(const ListView *lv)
static void bprint_ring(AVBPrint *script, int cx, int cy, int r)
int list_view_click(ListView *lv, int x, int y, int *chosen)
Handles a left click at window coordinates: a click on an entry chooses it, a click on a column butto...
@ LIST_SORT_NUMBER
Definition ffplay_list.c:46
@ LIST_SORT_TITLE
Definition ffplay_list.c:48
@ LIST_SORT_LENGTH
Definition ffplay_list.c:49
@ LIST_SORT_ARTIST
Definition ffplay_list.c:47
@ LIST_SORT_SHUFFLE
Definition ffplay_list.c:50
@ LIST_SORT_NB
Definition ffplay_list.c:51
#define HOVER_COLOR
void list_view_move(ListView *lv, int delta)
Moves the selection by delta rows.
void list_view_clear_search(ListView *lv)
Clears the search, listing all entries.
#define LIST_FADE_TIME
Definition ffplay_list.c:41
#define LIST_ROWS
Definition ffplay_list.c:43
void list_view_free(ListView **plv)
void list_view_hover(ListView *lv, int x, int y)
Keeps the mouse position, in window coordinates, -1 when the mouse left the window.
static void cache_entry_free(ListCacheEntry **entry)
static void list_select(ListView *lv, int row)
const char * label
int list_view_visible(const ListView *lv)
#define LIST_EVENT
static const struct @063245031310031234025313313056123246302351010020 sort_buttons[LIST_SORT_NB]
static ListHit list_hit(const ListLayout *l, int x, int y)
static int strip_add(ListView *lv, ListStrip **strips, int *nb, SDL_Rect rect, uint32_t background, const AVBPrint *script)
static int strip_render(ListView *lv, ListCacheEntry *entry)
void list_view_pin(ListView *lv, int pinned)
Keeps the list on screen and lets it take the keyboard and text input, or lets it fade out again.
static void cache_make_room(ListView *lv, size_t bytes)
#define PANEL_COLOR
static void list_update_rows(ListView *lv)
int list_view_update(ListView *lv)
Brings the list up to date for the next frame, once the pending input is handled.
#define CMP(a, b)
void list_view_text(ListView *lv, const char *text)
Appends text typed while the list is pinned to the search, or erases its last character when text is ...
static void list_sort_by(ListView *lv, int key, int seed_step)
void list_view_draw(ListView *lv)
Draws the list onto the window, between its other content and the present.
static void bprint_styles(AVBPrint *script, const ListLayout *l)
#define LIST_HOLD_TIME
Definition ffplay_list.c:42
int list_view_key(ListView *lv, SDL_Keycode key)
Handles a key while the list is pinned: escape unpins it, up/down move the selection,...
void list_view_clear(ListView *lv)
Removes the entries, which may be freed after this, and resets the sort and the search.
A list overlay for ffplay: entries with title, artist, length and metadata that the user can sort,...
static int av_bprint_is_complete(const AVBPrint *buf)
Test if the print buffer is complete (not truncated).
Definition bprint.h:218
int av_bprint_finalize(AVBPrint *buf, char **ret_str)
Finalize a print buffer.
Definition bprint.c:234
void av_bprint_chars(AVBPrint *buf, char c, unsigned n)
Append char c n times to a print buffer.
Definition bprint.c:129
void av_bprint_clear(AVBPrint *buf)
Reset the string to "" but keep internal allocated data.
Definition bprint.c:226
const AVDictionaryEntry * av_dict_iterate(const AVDictionary *m, const AVDictionaryEntry *prev)
Iterate over a dictionary.
Definition dict.c:43
int av_dict_count(const AVDictionary *m)
Get number of entries in dictionary.
Definition dict.c:38
#define AVERROR_EXTERNAL
Generic error in an external library.
Definition error.h:59
#define av_err2str(errnum)
Convenience macro, the return value should be used only directly in function arguments but never stan...
Definition error.h:122
#define AVERROR(e)
Definition error.h:45
#define AV_LOG_TRACE
Extremely verbose debugging, useful for libav* development.
Definition log.h:236
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition log.h:231
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition log.h:216
#define AV_LOG_FATAL
Something went wrong and recovery is not possible.
Definition log.h:204
#define AV_LOG_VERBOSE
Detailed information.
Definition log.h:226
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition log.h:210
void av_vlog(void *avcl, int level, const char *fmt, va_list vl)
Send the specified message to the log if the level is less than or equal to the current av_log_level.
Definition log.c:459
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd)
Rescale a 64-bit integer with specified rounding.
Definition mathematics.c:58
@ AV_ROUND_NEAR_INF
Round to nearest and halfway cases away from zero.
void * av_dynarray2_add(void **tab_ptr, int *nb_ptr, size_t elem_size, const uint8_t *elem_data)
Add an element of size elem_size to a dynamic array.
Definition mem.c:447
int av_dynarray_add_nofree(void *tab_ptr, int *nb_ptr, void *elem)
Add an element to a dynamic array.
Definition mem.c:419
static void * av_x_if_null(const void *p, const void *x)
Return x default pointer in case p is NULL.
Definition avutil.h:311
size_t av_strlcat(char *dst, const char *src, size_t size)
Append the string src to the string dst, but to a total length of no more than size - 1 bytes,...
Definition avstring.c:95
char * av_stristr(const char *s1, const char *s2)
Locate the first case-independent occurrence in the string haystack of the string needle.
Definition avstring.c:58
int av_strcasecmp(const char *a, const char *b)
Locale-independent case-insensitive compare.
Definition avstring.c:208
#define AV_TIME_BASE
Internal time base represented as integer.
Definition avutil.h:253
int index
Definition gxfenc.c:90
int a
static const int16_t alpha[]
Definition ilbcdata.h:55
#define r
Definition input.c:42
#define b
Definition input.c:43
static int shift(int a, int b)
Definition bonk.c:261
uint8_t w
Definition llvidencdsp.c:39
#define FFMIN(a, b)
Definition macros.h:49
#define FFMAX(a, b)
Definition macros.h:47
#define FFDIFFSIGN(x, y)
Comparator.
Definition macros.h:45
Memory handling functions.
static av_always_inline int cmp(MPVEncContext *const s, const int x, const int y, const int subx, const int suby, const int size, const int h, int ref_index, int src_index, me_cmp_func cmp_func, me_cmp_func chroma_cmp_func, const int flags)
compares a block (either a full macroblock or a partition thereof) against a proposed motion-compensa...
Definition motion_est.c:263
uint32_t tag
Definition movenc.c:2128
#define av_strdup(s)
Definition ops_static.c:55
#define AV_MSORT(p, tmp, num, type, cmp)
Merge sort, this sort requires a temporary buffer and is stable, its worst case time is O(n log n)
Definition qsort.h:103
#define FF_ARRAY_ELEMS(a)
uint32_t background
Definition ffplay_list.c:70
uint8_t * pixels
Definition ffplay_list.c:72
unsigned last_used
Definition ffplay_list.c:73
const char * title
Definition ffplay_list.h:37
AVDictionary * metadata
shown when the mouse is over the entry's symbol, may be NULL
Definition ffplay_list.h:40
const char * artist
Definition ffplay_list.h:38
int64_t length
in AV_TIME_BASE units, negative when unknown
Definition ffplay_list.h:39
int button
Definition ffplay_list.c:63
int detail
Definition ffplay_list.c:64
int inside
Definition ffplay_list.c:61
int button_y[LIST_SORT_NB]
int button_x[LIST_SORT_NB]
SDL_Rect rect
Definition ffplay_list.c:78
ListCacheEntry * entry
Definition ffplay_list.c:79
SDL_Rect dirty
Definition ffplay_list.c:85
int nb_entries
ListSortKey sort[LIST_SORT_NB]
int has_artist
ListCacheEntry ** cache
Definition ffplay_list.c:95
int nb_cache
Definition ffplay_list.c:96
int render_pending
uint32_t reciprocal[256]
int libass_missing_reported
size_t cache_bytes
Definition ffplay_list.c:97
ListStrip * strips
Definition ffplay_list.c:93
unsigned frame
Definition ffplay_list.c:98
int nb_strips
Definition ffplay_list.c:94
char search[64]
int * sort_tmp
SDL_Rect rect
Definition ffplay_list.c:87
int64_t fade_start
ListHit hover
int64_t last_input
SDL_Renderer * renderer
Definition ffplay_list.c:83
SDL_Texture * texture
Definition ffplay_list.c:84
int has_length
uint8_t * bitmap
Definition ffplay_list.c:86
int * rows
double drawn_alpha
ListEntry * entries
Definition ffplay_list.c:99
int shuffle_seed
int w
Definition f_ebur128.c:78
int y
Definition f_ebur128.c:78
int h
Definition f_ebur128.c:78
int x
Definition f_ebur128.c:78
Definition rpzaenc.c:60
uint8_t level
Definition svq3.c:208
#define av_free(p)
#define av_malloc_array(a, b)
#define av_mallocz(s)
#define av_freep(p)
#define av_log(a,...)
#define height
Definition dsp.h:99
#define width
Definition dsp.h:99
int64_t av_gettime_relative(void)
Get the current time in microseconds since some unspecified starting point.
Definition time.c:57
static void copy(const float *p1, float *p2, const int length)
float delta
int len