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 = (unsigned)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
331#if CONFIG_LIBASS
332static void bprint_styles(AVBPrint *script, const ListLayout *l)
333{
334 av_bprintf(script,
335 "[Script Info]\nScriptType: v4.00+\nPlayResX: %d\nPlayResY: %d\nWrapStyle: 2\nYCbCr Matrix: None\n\n"
336 "[V4+ Styles]\n"
337 "Format: Name, Fontname, Fontsize, PrimaryColour, OutlineColour, Bold, Outline, Alignment\n"
338 "Style: Button,Sans,%d,&H80FFFFFF,&H80FFFFFF,0,0,7\n"
339 "Style: Row,Sans,%d,&H00FFFFFF,&H00000000,0,%d,7\n"
340 "Style: Selected,Sans,%d,&H0080D0FF,&H00000000,-1,%d,7\n"
341 "Style: Artist,Sans,%d,&H00C0C0C0,&H00000000,0,%d,7\n"
342 "Style: ArtistSelected,Sans,%d,&H0080D0FF,&H00000000,-1,%d,7\n"
343 "Style: Playing,Sans,%d,&H00FFFFFF,&H00FFFFFF,0,0,7\n"
344 "Style: Hint,Sans,%d,&H00A0A0A0,&H00000000,0,%d,7\n"
345 "Style: ButtonHover,Sans,%d,&H50FFFFFF,&H50FFFFFF,0,0,7\n"
346 "Style: Ring,Sans,%d,&HFF000000,&H00A0A0A0,0,%d,7\n"
347 "Style: Detail,Sans,%d,&H00FFFFFF,&H00000000,0,%d,7\n\n"
348 "[Events]\nFormat: Start, End, Style, Text\n",
349 l->canvas_w, l->canvas_h, l->font, l->font, l->font / 16 + 1, l->font, l->font / 16 + 1,
350 l->font * 4 / 5, l->font / 16 + 1, l->font * 4 / 5, l->font / 16 + 1, l->font, l->font, l->font / 16 + 1,
351 l->font, l->font, l->font / 16 + 1, l->font * 4 / 5, l->font / 16 + 1);
352}
353#endif
354
355static void bprint_header_strip(const ListView *lv, AVBPrint *script, const ListLayout *l)
356{
357 if (lv->typing)
358 bprint_cell(script, *lv->search ? "Row" : "Hint", l->search_x, l->search_y, l->length_x,
359 l->search_y + l->font * 3 / 2, *lv->search ? lv->search : "type to search");
360 for (int b = 0; b < FF_ARRAY_ELEMS(sort_buttons); b++) {
361 int x = l->button_x[b], y = l->button_y[b], w = sort_buttons[b].width * l->font / 2;
362
363 if (x < 0)
364 continue;
365 bprint_box(script, b == lv->hover.button ? "ButtonHover" : "Button", x, y, w, l->font * 5 / 4);
366 av_bprintf(script, LIST_EVENT "%s,{\\an5\\pos(%d,%d)}", lv->sort[0].key == b ? "Selected" : "Row",
367 x + w / 2, y + l->font * 5 / 8);
368 if (b == LIST_SORT_SHUFFLE)
369 av_bprintf(script, "\xe2\x97\x82 %s %d \xe2\x96\xb8\n", sort_buttons[b].label, lv->shuffle_seed);
370 else
371 av_bprintf(script, "%s%s\n", sort_buttons[b].label,
372 lv->sort[0].key != b ? "" : lv->sort[0].descending ? " \xe2\x96\xbc" : " \xe2\x96\xb2");
373 }
374}
375
376static void bprint_row_strip(const ListView *lv, AVBPrint *script, const ListLayout *l, int r)
377{
378 int index = lv->rows[r];
379 const ListEntry *entry = &lv->entries[index];
380 const char *style = r == lv->selected ? "Selected" : "Row";
381 int64_t seconds;
382
383 if (index == lv->playing)
384 bprint_box(script, "Playing", 0, 0, l->font / 6, l->line);
385 av_bprintf(script, LIST_EVENT "%s,{\\an6\\pos(%d,%d)}%d\n", style, l->font * 5 / 2, l->line / 2, index + 1);
386 bprint_cell(script, style, l->text_x, l->font / 8, l->text_right, l->line, entry->title);
387 bprint_cell(script, r == lv->selected ? "ArtistSelected" : "Artist", l->text_x, l->font * 5 / 4,
388 l->text_right, l->line, entry->artist);
389 bprint_ring(script, l->detail_x, l->line / 2, l->font * 9 / 20);
390 av_bprintf(script, LIST_EVENT "Hint,{\\an5\\pos(%d,%d)}i\n", l->detail_x, l->line / 2);
391 if (entry->length < 0)
392 return;
393 seconds = av_rescale_rnd(entry->length, 1, AV_TIME_BASE, AV_ROUND_NEAR_INF);
394 av_bprintf(script, LIST_EVENT "%s,{\\an6\\pos(%d,%d)}", style, l->length_x, l->line / 2);
395 if (seconds >= 3600)
396 av_bprintf(script, "%"PRId64":%02d:%02d\n", seconds / 3600, (int)(seconds / 60 % 60), (int)(seconds % 60));
397 else
398 av_bprintf(script, "%d:%02d\n", (int)(seconds / 60), (int)(seconds % 60));
399}
400
402{
403 if (*entry) {
404 av_freep(&(*entry)->script);
405 av_freep(&(*entry)->pixels);
406 }
408}
409
410/* drops the least recently used strips not shown in this frame until the new one fits into twice the bitmap size */
411static void cache_make_room(ListView *lv, size_t bytes)
412{
413 while (lv->cache_bytes + bytes > 2 * (size_t)lv->rect.w * lv->rect.h * 4) {
414 int oldest = -1;
415
416 for (int i = 0; i < lv->nb_cache; i++)
417 if (lv->cache[i]->last_used != lv->frame && (oldest < 0 || lv->cache[i]->last_used < lv->cache[oldest]->last_used))
418 oldest = i;
419 if (oldest < 0)
420 return;
421 lv->cache_bytes -= (size_t)lv->cache[oldest]->w * lv->cache[oldest]->h * 4;
422 cache_entry_free(&lv->cache[oldest]);
423 lv->cache[oldest] = lv->cache[--lv->nb_cache];
424 }
425}
426
427static int strip_render(ListView *lv, ListCacheEntry *entry);
428
429/* the cached strip for the script, background and place, rendered when there is none */
430static int cache_get(ListView *lv, const SDL_Rect *rect, uint32_t background, const AVBPrint *script, ListCacheEntry **entry)
431{
432 int ret;
433
434 for (int i = 0; i < lv->nb_cache; i++) {
435 ListCacheEntry *e = lv->cache[i];
436
437 if (e->background == background && e->x == rect->x && e->w == rect->w && e->h == rect->h &&
438 !strcmp(e->script, script->str)) {
439 e->last_used = lv->frame;
440 *entry = e;
441 return 0;
442 }
443 }
444 cache_make_room(lv, (size_t)rect->w * rect->h * 4);
445 if (!(*entry = av_mallocz(sizeof(**entry))))
446 return AVERROR(ENOMEM);
447 **entry = (ListCacheEntry){ av_strdup(script->str), background, rect->x, rect->w, rect->h,
448 av_malloc_array(rect->w * rect->h, 4), lv->frame };
449 ret = (*entry)->script && (*entry)->pixels ? strip_render(lv, *entry) : AVERROR(ENOMEM);
450 if (ret >= 0)
452 if (ret < 0) {
454 return ret;
455 }
456 lv->cache_bytes += (size_t)rect->w * rect->h * 4;
457 return 0;
458}
459
460static int strip_add(ListView *lv, ListStrip **strips, int *nb, SDL_Rect rect, uint32_t background, const AVBPrint *script)
461{
463 ListStrip *strip;
464 int ret;
465
466 if (rect.w <= 0 || rect.h <= 0)
467 return 0;
468 if (!av_bprint_is_complete(script))
469 return AVERROR(ENOMEM);
470 if ((ret = cache_get(lv, &rect, background, script, &entry)) < 0)
471 return ret;
472 if (!(strip = av_dynarray2_add((void **)strips, nb, sizeof(*strip), NULL)))
473 return AVERROR(ENOMEM);
474 *strip = (ListStrip){ rect, entry };
475 return 0;
476}
477
478/* the strips the list consists of right now: the header, each visible row, the bottom padding and the metadata popup,
479 * beside the list or over its rows when the window leaves no room there */
480static int list_strips(ListView *lv, const ListLayout *l, ListStrip **strips, int *nb)
481{
482 AVBPrint script;
483 int ret = 0;
484
486 for (int i = 0; ret >= 0 && i < l->nb_rows + 2; i++) {
487 int row = i && i <= l->nb_rows ? l->first + i - 1 : -1;
488 SDL_Rect rect = { 0, i ? l->rows_y + (i - 1) * l->line : 0, l->width, i ? l->line : l->rows_y };
489
490 if (i > l->nb_rows)
491 rect.h = l->font / 2;
492 rect.h = FFMIN(rect.h, l->canvas_h - rect.y);
493 av_bprint_clear(&script);
494 if (!i)
495 bprint_header_strip(lv, &script, l);
496 else if (row >= 0)
497 bprint_row_strip(lv, &script, l, row);
498 ret = strip_add(lv, strips, nb, rect, row >= 0 && row == lv->hover.row ? HOVER_COLOR : PANEL_COLOR, &script);
499 }
500 if (ret >= 0 && lv->hover.detail) {
501 const AVDictionary *metadata = lv->entries[lv->rows[lv->hover.row]].metadata;
502 const AVDictionaryEntry *tag = NULL;
503 int beside = l->canvas_w - l->width >= l->font * 12;
504 int height = FFMIN(av_dict_count(metadata) * l->font + l->font / 2, l->canvas_h);
505 int x = beside ? l->width + l->font / 2 : l->text_x;
506 int right = beside ? l->canvas_w : l->length_x - l->font / 2;
507 SDL_Rect rect = { x, av_clip(l->rows_y + (lv->hover.row - l->first) * l->line, 0, l->canvas_h - height),
508 right - x, height };
509
510 av_bprint_clear(&script);
511 for (int top = l->font / 4; top < height + l->font && (tag = av_dict_iterate(metadata, tag)); top += l->font) {
512 bprint_cell(&script, "Detail", x + l->font / 2, top, x + l->font * 7, height, tag->key);
513 bprint_cell(&script, "Detail", x + l->font * 15 / 2, top, right - l->font / 4, height, tag->value);
514 }
515 ret = strip_add(lv, strips, nb, rect, POPUP_COLOR, &script);
516 }
517 av_bprint_finalize(&script, NULL);
518 return ret;
519}
520
521static void list_discard(ListView *lv)
522{
523 av_freep(&lv->bitmap);
524 av_freep(&lv->strips);
525 lv->nb_strips = 0;
526 for (int i = 0; i < lv->nb_cache; i++)
527 cache_entry_free(&lv->cache[i]);
528 av_freep(&lv->cache);
529 lv->nb_cache = 0;
530 lv->cache_bytes = 0;
531 if (lv->texture)
532 SDL_DestroyTexture(lv->texture);
533 lv->texture = NULL;
534#if CONFIG_LIBASS
535 if (lv->track)
536 ass_free_track(lv->track);
537 lv->track = NULL;
538#endif
539}
540
541#if CONFIG_LIBASS
542static void libass_log(int libass_level, const char *fmt, va_list args, void *opaque)
543{
544 static const int levels[] = { AV_LOG_FATAL, AV_LOG_ERROR, AV_LOG_WARNING, AV_LOG_WARNING,
546 int level = levels[av_clip(libass_level, 0, FF_ARRAY_ELEMS(levels) - 1)];
547
548 av_vlog(NULL, level, fmt, args);
549 av_log(NULL, level, "\n");
550}
551#endif
552
553/* the bitmap, the texture it is uploaded to, and libass with the styles of the layout, rendering a strip at a time */
554static int list_configure(ListView *lv, const ListLayout *l, const SDL_Rect *canvas)
555{
556#if CONFIG_LIBASS
557 AVBPrint styles;
558
559 list_discard(lv);
560 lv->rect = *canvas;
561 if (l->canvas_w <= 0 || l->canvas_h <= 0 || l->canvas_w > INT_MAX / 4 / l->canvas_h)
562 return AVERROR(EINVAL);
563 if (!(lv->bitmap = av_mallocz(l->canvas_w * l->canvas_h * 4)))
564 return AVERROR(ENOMEM);
565 if (!(lv->texture = SDL_CreateTexture(lv->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING,
566 l->canvas_w, l->canvas_h)) ||
567 SDL_SetTextureBlendMode(lv->texture, SDL_BLENDMODE_BLEND) < 0)
568 return AVERROR_EXTERNAL;
569 lv->dirty = (SDL_Rect){ 0, 0, l->canvas_w, l->canvas_h };
570 if (!lv->library) {
571 if (!(lv->library = ass_library_init()))
572 return AVERROR(ENOMEM);
573 ass_set_message_cb(lv->library, libass_log, NULL);
574 ass_set_extract_fonts(lv->library, 1);
575 if (!(lv->ass = ass_renderer_init(lv->library))) {
576 ass_library_done(lv->library);
577 lv->library = NULL;
578 return AVERROR(ENOMEM);
579 }
580 ass_set_fonts(lv->ass, NULL, NULL, 1, NULL, 1);
581 }
583 bprint_styles(&styles, l);
584 if (av_bprint_is_complete(&styles) && (lv->track = ass_new_track(lv->library)))
585 ass_process_codec_private(lv->track, styles.str, styles.len);
586 av_bprint_finalize(&styles, NULL);
587 if (!lv->track)
588 return AVERROR(ENOMEM);
589 ass_set_frame_size(lv->ass, l->canvas_w, l->canvas_h);
590 ass_set_storage_size(lv->ass, l->canvas_w, l->canvas_h);
591 return 0;
592#else
593 if (!lv->libass_missing_reported++)
594 av_log(NULL, AV_LOG_ERROR, "The list needs libass, which this build lacks\n");
595 return AVERROR(ENOSYS);
596#endif
597}
598
599#if CONFIG_LIBASS
600/* the color with opacity a over the pixel, both ARGB, using the 16.16 reciprocals of the alpha values */
601static uint32_t blend_over(uint32_t dst, uint32_t rgb, int a, const uint32_t *reciprocal)
602{
603 int dst_a = dst >> 24, out_a = a + (dst_a * (255 - a) + 127) / 255;
604 uint32_t out = (uint32_t)out_a << 24;
605
606 for (int shift = 0; shift < 24; shift += 8) {
607 int premultiplied = ((rgb >> shift & 0xFF) * a * 255 + (dst >> shift & 0xFF) * dst_a * (255 - a) + 255 * 127) / (255 * 255);
608
609 out |= FFMIN(premultiplied * reciprocal[out_a] >> 16, 255) << shift;
610 }
611 return out;
612}
613#endif
614
615/* renders the strip over its background */
617{
618#if CONFIG_LIBASS
619 uint32_t *pixels = (uint32_t *)entry->pixels;
620 int w = entry->w, h = entry->h;
621
622 ass_flush_events(lv->track);
623 ass_process_data(lv->track, entry->script, strlen(entry->script));
624 for (int i = 0; i < w * h; i++)
625 pixels[i] = entry->background;
626 for (const ASS_Image *image = ass_render_frame(lv->ass, lv->track, 0, NULL); image; image = image->next) {
627 int opacity = 255 - (image->color & 0xFF);
628 int x0 = FFMAX(image->dst_x, entry->x), x1 = FFMIN(image->dst_x + image->w, entry->x + w);
629
630 for (int y = FFMAX(image->dst_y, 0); x0 < x1 && y < FFMIN(image->dst_y + image->h, h); y++) {
631 const uint8_t *coverage = image->bitmap + (y - image->dst_y) * image->stride + x0 - image->dst_x;
632 uint32_t *dst = pixels + y * w + x0 - entry->x;
633
634 for (int x = 0; x < x1 - x0; x++) {
635 int a = (opacity * coverage[x] + 127) / 255;
636
637 if (a)
638 dst[x] = blend_over(dst[x], image->color >> 8, a, lv->reciprocal);
639 }
640 }
641 }
642 return 0;
643#else
644 return AVERROR(ENOSYS);
645#endif
646}
647
648/* brings the bitmap up to date: what differs from the strips shown before is cleared and composed again from the strips
649 * covering it, rendering only strips that are not cached */
650static int list_render(ListView *lv)
651{
652 ListLayout l = list_layout(lv);
653 SDL_Rect canvas = { l.font, l.font, l.canvas_w, l.canvas_h }, dirty = { 0, 0, 0, 0 };
654 ListStrip *strips = NULL;
655 int nb = 0, ret = 0;
656
657 lv->render_pending = 0;
658 lv->hover = list_hit(&l, lv->mouse_x, lv->mouse_y);
659 /* the strips shown stay cached until compared with the new ones */
660 lv->frame++;
661 for (int i = 0; i < lv->nb_strips; i++)
662 lv->strips[i].entry->last_used = lv->frame;
663 if (!lv->bitmap || !SDL_RectEquals(&lv->rect, &canvas))
664 ret = list_configure(lv, &l, &canvas);
665 if (ret >= 0)
666 ret = list_strips(lv, &l, &strips, &nb);
667 for (int i = 0; ret >= 0 && i < FFMAX(nb, lv->nb_strips); i++) {
668 const ListStrip *old = i < lv->nb_strips ? &lv->strips[i] : NULL, *new = i < nb ? &strips[i] : NULL;
669
670 if (old && new && old->entry == new->entry && SDL_RectEquals(&old->rect, &new->rect))
671 continue;
672 if (old)
673 SDL_UnionRect(&dirty, &old->rect, &dirty);
674 if (new)
675 SDL_UnionRect(&dirty, &new->rect, &dirty);
676 }
677 if (ret >= 0 && dirty.w) {
678 for (int y = dirty.y; y < dirty.y + dirty.h; y++)
679 memset(lv->bitmap + (y * lv->rect.w + dirty.x) * 4, 0, dirty.w * 4);
680 for (int i = 0; i < nb; i++) {
681 const ListCacheEntry *entry = strips[i].entry;
682 SDL_Rect part;
683
684 if (!SDL_IntersectRect(&strips[i].rect, &dirty, &part))
685 continue;
686 for (int y = part.y; y < part.y + part.h; y++)
687 memcpy(lv->bitmap + (y * lv->rect.w + part.x) * 4,
688 entry->pixels + ((y - strips[i].rect.y) * entry->w + part.x - strips[i].rect.x) * 4, part.w * 4);
689 }
690 SDL_UnionRect(&lv->dirty, &dirty, &lv->dirty);
691 lv->changed = 1;
692 }
693 if (ret < 0) {
694 if (ret != AVERROR(ENOSYS))
695 av_log(NULL, AV_LOG_ERROR, "Could not render the list: %s\n", av_err2str(ret));
696 list_discard(lv);
697 av_freep(&strips);
698 nb = 0;
699 if (lv->pinned)
700 SDL_StopTextInput();
701 lv->pinned = 0;
702 }
703 av_freep(&lv->strips);
704 lv->strips = strips;
705 lv->nb_strips = nb;
706 return ret;
707}
708
709static void list_select(ListView *lv, int row)
710{
712
713 lv->selected = row;
714 lv->fade_start = now - list_alpha(lv, now) * LIST_FADE_TIME;
715 lv->last_input = now;
716 lv->render_pending = 1;
717}
718
719void list_view_text(ListView *lv, const char *text)
720{
721 int len = strlen(lv->search);
722
723 if (text) {
724 if (len + strlen(text) < sizeof(lv->search))
725 av_strlcat(lv->search, text, sizeof(lv->search));
726 } else if (len) {
727 while (len > 0 && (lv->search[--len] & 0xC0) == 0x80)
728 ;
729 lv->search[len] = 0;
730 }
732 lv->render_pending = 1;
733}
734
736{
737 ListView *lv = av_mallocz(sizeof(*lv));
738
739 if (!lv)
740 return NULL;
741 lv->renderer = renderer;
742 lv->playing = -1;
743 lv->mouse_x = lv->mouse_y = -1;
744 lv->hover = (ListHit){ 0, -1, -1, 0 };
745 lv->reciprocal[0] = 1 << 16;
746 for (int a = 1; a < 256; a++)
747 lv->reciprocal[a] = ((255 << 16) + a - 1) / a;
748 list_view_clear(lv);
749 return lv;
750}
751
753{
754 ListView *lv = *plv;
755
756 if (!lv)
757 return;
758 list_discard(lv);
759#if CONFIG_LIBASS
760 if (lv->ass)
761 ass_renderer_done(lv->ass);
762 if (lv->library)
763 ass_library_done(lv->library);
764#endif
765 av_freep(&lv->entries);
766 av_freep(plv);
767}
768
769int list_view_show(ListView *lv, const ListEntry *entries, int nb_entries, int selected)
770{
772 int previous = list_view_selected(lv);
773
774 if (!lv->renderer)
775 return AVERROR(EINVAL);
776 /* the rows and the sort scratch follow the entries */
777 copy = av_malloc_array(nb_entries, sizeof(*copy) + 2 * sizeof(*lv->rows));
778 if (!copy)
779 return AVERROR(ENOMEM);
780 if (nb_entries)
781 memcpy(copy, entries, nb_entries * sizeof(*entries));
782 av_free(lv->entries);
783 lv->entries = copy;
784 lv->rows = (int *)(copy + nb_entries);
785 lv->sort_tmp = lv->rows + nb_entries;
786 lv->nb_entries = nb_entries;
787 lv->nb_rows = 0;
788 lv->has_artist = lv->has_length = 0;
789 for (int i = 0; i < nb_entries; i++) {
790 lv->has_artist |= entries[i].artist && *entries[i].artist;
791 lv->has_length |= entries[i].length >= 0;
792 }
794 for (int row = 0; row < lv->nb_rows; row++)
795 if (lv->rows[row] == previous)
796 lv->selected = row;
797 for (int row = 0; row < lv->nb_rows; row++)
798 if (lv->rows[row] == selected)
799 lv->selected = row;
800 list_select(lv, lv->selected);
801 return 0;
802}
803
805{
806 lv->nb_entries = lv->nb_rows = lv->selected = 0;
807 lv->search[0] = 0;
808 for (int i = 0; i < LIST_SORT_NB; i++)
809 lv->sort[i] = (ListSortKey){ i, 0 };
810 lv->shuffle_seed = 0;
811 list_discard(lv);
812}
813
814void list_view_pin(ListView *lv, int pinned)
815{
816 lv->pinned = pinned;
817 if (pinned) {
818 SDL_StartTextInput();
819 lv->typing = 1;
820 } else {
821 SDL_StopTextInput();
823 }
824 lv->render_pending = 1;
825}
826
828{
829 return lv->pinned;
830}
831
833{
834 return lv->pinned || list_alpha(lv, av_gettime_relative()) > 0;
835}
836
838{
839 return lv->nb_rows ? lv->rows[lv->selected] : -1;
840}
841
843{
844 if (lv->nb_rows)
845 list_select(lv, av_clip(lv->selected + delta, 0, lv->nb_rows - 1));
846}
847
848int list_view_key(ListView *lv, SDL_Keycode key)
849{
850 switch (key) {
851 case SDLK_ESCAPE: list_view_pin(lv, 0); break;
852 case SDLK_UP: list_view_move(lv, -1); break;
853 case SDLK_DOWN: list_view_move(lv, 1); break;
854 case SDLK_BACKSPACE: list_view_text(lv, NULL); break;
855 case SDLK_RETURN:
856 case SDLK_KP_ENTER: return list_view_selected(lv);
857 }
858 return -1;
859}
860
862{
863 lv->search[0] = 0;
865 lv->render_pending = 1;
866}
867
868/* asks for a render when what a visible list points at changed; the render waits until the pending events are handled,
869 * so a sweep across the list renders once */
870void list_view_hover(ListView *lv, int x, int y)
871{
872 ListLayout l;
873 ListHit hit;
874
875 lv->mouse_x = x;
876 lv->mouse_y = y;
877 if (!list_view_visible(lv))
878 return;
879 l = list_layout(lv);
880 hit = list_hit(&l, x, y);
881 if (hit.row != lv->hover.row || hit.button != lv->hover.button || hit.detail != lv->hover.detail)
882 lv->render_pending = 1;
883}
884
885int list_view_click(ListView *lv, int x, int y, int *chosen)
886{
887 ListLayout l = list_layout(lv);
888 ListHit hit = list_hit(&l, x, y);
889
890 *chosen = hit.row >= 0 && !hit.detail ? lv->rows[hit.row] : -1;
891 if (hit.button >= 0) {
892 int button_w = sort_buttons[hit.button].width * l.font / 2;
893
894 list_sort_by(lv, hit.button, (x - l.font - l.button_x[hit.button]) * 2 < button_w ? -1 : 1);
896 list_select(lv, lv->selected);
897 }
898 return hit.inside;
899}
900
902{
903 if (lv->width == width && lv->height == height)
904 return;
905 lv->width = width;
906 lv->height = height;
907 if (list_view_visible(lv))
908 lv->render_pending = 1;
909 else
910 list_discard(lv);
911}
912
914{
915 if (lv->playing == entry)
916 return;
917 lv->playing = entry;
918 lv->render_pending = 1;
919}
920
922{
923 double alpha = list_alpha(lv, av_gettime_relative());
924 int changed;
925
926 if (lv->typing && !lv->pinned && alpha <= 0) {
927 lv->typing = 0;
928 lv->search[0] = 0;
930 }
931 if (lv->render_pending && lv->width)
932 list_render(lv);
933 changed = lv->changed || alpha != lv->drawn_alpha;
934 lv->changed = 0;
935 return changed;
936}
937
938/* uploads what the texture lacks of the bitmap, at most once per refresh */
940{
941 double alpha = list_alpha(lv, av_gettime_relative());
942 SDL_Rect *dirty = &lv->dirty;
943
944 lv->drawn_alpha = alpha;
945 if (alpha <= 0)
946 return;
947 if (dirty->w && !SDL_UpdateTexture(lv->texture, dirty, lv->bitmap + (dirty->y * lv->rect.w + dirty->x) * 4, lv->rect.w * 4))
948 *dirty = (SDL_Rect){ 0, 0, 0, 0 };
949 SDL_SetTextureAlphaMod(lv->texture, alpha * 255);
950 SDL_RenderCopy(lv->renderer, lv->texture, NULL, &lv->rect);
951}
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.
#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:465
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