1 #include "builddef.h"
2 #ifdef USE_FFMPEG
3 #include <libavutil/error.h>
4 #include <libavutil/frame.h>
5 #include <libavutil/pixdesc.h>
6 #include <libavutil/version.h>
7 #include <libavutil/imgutils.h>
8 #include <libavcodec/avcodec.h>
9 #include <libavutil/rational.h>
10 #include <libswscale/swscale.h>
11 #include <libswscale/version.h>
12 #include <libavformat/version.h>
13 #include <libavformat/avformat.h>
14 #include "lib/visual-details.h"
15 #include "lib/internal.h"
16 
17 struct AVFormatContext;
18 struct AVCodecContext;
19 struct AVFrame;
20 struct AVCodec;
21 struct AVCodecParameters;
22 struct AVPacket;
23 
24 typedef struct ncvisual_details {
25   struct AVFormatContext* fmtctx;
26   struct AVCodecContext* codecctx;     // video codec context
27   struct AVCodecContext* subtcodecctx; // subtitle codec context
28   struct AVFrame* frame;               // frame as read/loaded/converted
29   struct AVCodec* codec;
30   struct AVCodec* subtcodec;
31   struct AVPacket* packet;
32   struct SwsContext* swsctx;
33   struct SwsContext* rgbactx;
34   AVSubtitle subtitle;
35   int stream_index;        // match against this following av_read_frame()
36   int sub_stream_index;    // subtitle stream index, can be < 0 if no subtitles
37   bool packet_outstanding;
38 } ncvisual_details;
39 
40 #define IMGALLOCALIGN 64
41 
42 /*static void
43 print_frame_summary(const AVCodecContext* cctx, const AVFrame* f){
44   if(f == NULL){
45     fprintf(stderr, "NULL frame\n");
46     return;
47   }
48   char pfmt[128];
49   av_get_pix_fmt_string(pfmt, sizeof(pfmt), f->format);
50   fprintf(stderr, "Frame %05d %p (%d? %d?) %dx%d pfmt %d (%s)\n",
51           cctx ? cctx->frame_number : 0, f,
52           f->coded_picture_number,
53           f->display_picture_number,
54           f->width, f->height,
55           f->format, pfmt);
56   fprintf(stderr, " Data (%d):", AV_NUM_DATA_POINTERS);
57   int i;
58   for(i = 0 ; i < AV_NUM_DATA_POINTERS ; ++i){
59     fprintf(stderr, " %p", f->data[i]);
60   }
61   fprintf(stderr, "\n Linesizes:");
62   for(i = 0 ; i < AV_NUM_DATA_POINTERS ; ++i){
63     fprintf(stderr, " %d", f->linesize[i]);
64   }
65   if(f->sample_aspect_ratio.num == 0 && f->sample_aspect_ratio.den == 1){
66     fprintf(stderr, "\n Aspect ratio unknown");
67   }else{
68     fprintf(stderr, "\n Aspect ratio %d:%d", f->sample_aspect_ratio.num, f->sample_aspect_ratio.den);
69   }
70   if(f->interlaced_frame){
71     fprintf(stderr, " [ILaced]");
72   }
73   if(f->palette_has_changed){
74     fprintf(stderr, " [NewPal]");
75   }
76   fprintf(stderr, " PTS %" PRId64 " Flags: 0x%04x\n", f->pts, f->flags);
77   fprintf(stderr, " %" PRIu64 "ms@%" PRIu64 "ms (%skeyframe) qual: %d\n",
78           f->pkt_duration, // FIXME in 'time_base' units
79           f->best_effort_timestamp,
80           f->key_frame ? "" : "non-",
81           f->quality);
82 }*/
83 
84 static char*
deass(const char * ass)85 deass(const char* ass){
86   // SSA/ASS formats:
87   // Dialogue: Marked=0,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,0000,,Et les enregistrements de ses ondes delta ?
88   // FIXME more
89   if(strncmp(ass, "Dialogue:", strlen("Dialogue:"))){
90     return NULL;
91   }
92   const char* delim = strchr(ass, ',');
93   int commas = 0; // we want 8
94   while(delim && commas < 8){
95     delim = strchr(delim + 1, ',');
96     ++commas;
97   }
98   if(!delim){
99     return NULL;
100   }
101   // handle ASS syntax...\i0, \b0, etc.
102   char* dup = strdup(delim + 1);
103   char* c = dup;
104   while(*c){
105     if(*c == '\\'){
106       *c = ' ';
107       ++c;
108       if(*c){
109         *c = ' ';;
110       }
111     }
112     ++c;
113   }
114   return dup;
115 }
116 
117 static struct ncplane*
subtitle_plane_from_text(ncplane * parent,const char * text)118 subtitle_plane_from_text(ncplane* parent, const char* text){
119   if(parent == NULL){
120 //logerror("need a parent plane\n");
121     return NULL;
122   }
123   int width = ncstrwidth(text, NULL, NULL);
124   if(width <= 0){
125 //logwarn("couldn't extract subtitle from %s\n", text);
126     return NULL;
127   }
128   int rows = (width + ncplane_dim_x(parent) - 1) / ncplane_dim_x(parent);
129   struct ncplane_options nopts = {
130     .y = ncplane_dim_y(parent) - (rows + 1),
131     .rows = rows,
132     .cols = ncplane_dim_x(parent),
133     .name = "subt",
134   };
135   struct ncplane* n = ncplane_create(parent, &nopts);
136   if(n == NULL){
137 //logerror("error creating subtitle plane\n");
138     return NULL;
139   }
140   uint64_t channels = 0;
141   ncchannels_set_fg_alpha(&channels, NCALPHA_HIGHCONTRAST);
142   ncchannels_set_fg_rgb8(&channels, 0x88, 0x88, 0x88);
143   ncplane_stain(n, -1, -1, 0, 0, channels, channels, channels, channels);
144   ncchannels_set_fg_default(&channels);
145   ncplane_puttext(n, 0, NCALIGN_LEFT, text, NULL);
146   ncchannels_set_bg_alpha(&channels, NCALPHA_TRANSPARENT);
147   ncplane_set_base(n, " ", 0, channels);
148   return n;
149 }
150 
151 static uint32_t palette[NCPALETTESIZE];
152 
ffmpeg_subtitle(ncplane * parent,const ncvisual * ncv)153 struct ncplane* ffmpeg_subtitle(ncplane* parent, const ncvisual* ncv){
154   for(unsigned i = 0 ; i < ncv->details->subtitle.num_rects ; ++i){
155     // it is possible that there are more than one subtitle rects present,
156     // but we only bother dealing with the first one we find FIXME?
157     const AVSubtitleRect* rect = ncv->details->subtitle.rects[i];
158     if(rect->type == SUBTITLE_ASS){
159       char* ass = deass(rect->ass);
160       struct ncplane* n = NULL;
161       if(ass){
162         n = subtitle_plane_from_text(parent, ass);
163       }
164       free(ass);
165       return n;
166     }else if(rect->type == SUBTITLE_TEXT){;
167       return subtitle_plane_from_text(parent, rect->text);
168     }else if(rect->type == SUBTITLE_BITMAP){
169       // there are technically up to AV_NUM_DATA_POINTERS planes, but we
170       // only try to work with the first FIXME?
171       if(rect->linesize[0] != rect->w){
172 //logwarn("bitmap subtitle size %d != width %d\n", rect->linesize[0], rect->w);
173         continue;
174       }
175       struct notcurses* nc = ncplane_notcurses(parent);
176       const unsigned cellpxy = ncplane_pile_const(parent)->cellpxy;
177       const unsigned cellpxx = ncplane_pile_const(parent)->cellpxx;
178       if(cellpxy <= 0 || cellpxx <= 0){
179         continue;
180       }
181       struct ncvisual* v = ncvisual_from_palidx(rect->data[0], rect->h,
182                                                 rect->w, rect->w,
183                                                 NCPALETTESIZE, 1, palette);
184       if(v == NULL){
185         return NULL;
186       }
187       int rows = (rect->h + cellpxx - 1) / cellpxy;
188       struct ncplane_options nopts = {
189         .rows = rows,
190         .cols = (rect->w + cellpxx - 1) / cellpxx,
191         .y = ncplane_dim_y(parent) - rows - 1,
192         .name = "t1st",
193       };
194       struct ncplane* vn = ncplane_create(parent, &nopts);
195       if(vn == NULL){
196         ncvisual_destroy(v);
197         return NULL;
198       }
199       struct ncvisual_options vopts = {
200         .n = vn,
201         .blitter = NCBLIT_PIXEL,
202         .scaling = NCSCALE_STRETCH,
203       };
204       if(ncvisual_blit(nc, v, &vopts) == NULL){
205         ncplane_destroy(vn);
206         ncvisual_destroy(v);
207         return NULL;
208       }
209       ncvisual_destroy(v);
210       return vn;
211     }
212   }
213   return NULL;
214 }
215 
216 static int
averr2ncerr(int averr)217 averr2ncerr(int averr){
218   if(averr == AVERROR_EOF){
219     return 1;
220   }
221   // FIXME need to map averror codes to ncerrors
222 //fprintf(stderr, "AVERR: %d/%x %d/%x\n", averr, averr, -averr, -averr);
223   return -1;
224 }
225 
226 // force an AVImage to RGBA for safe use with the ncpixel API
227 static int
force_rgba(ncvisual * n)228 force_rgba(ncvisual* n){
229   const int targformat = AV_PIX_FMT_RGBA;
230   AVFrame* inf = n->details->frame;
231 //fprintf(stderr, "%p got format: %d (%d/%d) want format: %d (%d/%d)\n", n->details->frame, inf->format, n->pixy, n->pixx, targformat);
232   if(inf->format == targformat){
233     return 0;
234   }
235   AVFrame* sframe = av_frame_alloc();
236   if(sframe == NULL){
237 //fprintf(stderr, "Couldn't allocate output frame for scaled frame\n");
238     return -1;
239   }
240 //fprintf(stderr, "WHN NCV: %d/%d\n", inf->width, inf->height);
241   n->details->rgbactx = sws_getCachedContext(n->details->rgbactx,
242                                             inf->width, inf->height, inf->format,
243                                             inf->width, inf->height, targformat,
244                                             SWS_LANCZOS, NULL, NULL, NULL);
245   if(n->details->rgbactx == NULL){
246 //fprintf(stderr, "Error retrieving details->rgbactx\n");
247     return -1;
248   }
249   memcpy(sframe, inf, sizeof(*inf));
250   sframe->format = targformat;
251   sframe->width = inf->width;
252   sframe->height = inf->height;
253   int size = av_image_alloc(sframe->data, sframe->linesize,
254                             sframe->width, sframe->height,
255                             sframe->format,
256                             IMGALLOCALIGN);
257   if(size < 0){
258 //fprintf(stderr, "Error allocating visual data (%d X %d)\n", sframe->height, sframe->width);
259     return -1;
260   }
261 //fprintf(stderr, "INFRAME DAA: %p SDATA: %p FDATA: %p\n", inframe->data[0], sframe->data[0], ncv->details->frame->data[0]);
262   int height = sws_scale(n->details->rgbactx, (const uint8_t* const*)inf->data,
263                          inf->linesize, 0, inf->height, sframe->data,
264                          sframe->linesize);
265   if(height < 0){
266 //fprintf(stderr, "Error applying converting %d\n", inf->format);
267     av_frame_free(&sframe);
268     return -1;
269   }
270   int bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(sframe->format));
271   if(bpp != 32){
272 //fprintf(stderr, "Bad bits-per-pixel (wanted 32, got %d)\n", bpp);
273     av_frame_free(&sframe);
274     return -1;
275   }
276   n->rowstride = sframe->linesize[0];
277   if((uint32_t*)sframe->data[0] != n->data){
278 //fprintf(stderr, "SETTING UP RESIZE %p\n", n->data);
279     if(n->details->frame){
280       if(n->owndata){
281         // we don't free the frame data here, because it's going to be
282         // freed (if appropriate) by ncvisual_set_data() momentarily.
283         av_freep(&n->details->frame);
284       }
285     }
286     ncvisual_set_data(n, sframe->data[0], true);
287   }
288   n->details->frame = sframe;
289   return 0;
290 }
291 
292 // turn arbitrary input packets into RGBA frames. reads packets until it gets
293 // a visual frame. a packet might contain several frames (this is typically
294 // true only of audio), and a frame might be carried across several packets.
295 // * avcodec_receive_frame() returns EAGAIN if it needs more packets.
296 // * avcodec_send_packet() returns EAGAIN if avcodec_receive_frame() needs
297 //    be called to extract further frames; in this case, the packet ought
298 //    be resubmitted once the existing frames are cleared.
299 static int
ffmpeg_decode(ncvisual * n)300 ffmpeg_decode(ncvisual* n){
301   if(n->details->fmtctx == NULL){ // not a file-backed ncvisual
302     return -1;
303   }
304   bool have_frame = false;
305   bool unref = false;
306   // note that there are two loops here; once we're out of the external one,
307   // we've either returned a failure, or we have a frame. averr2ncerr()
308   // translates AVERROR_EOF into a return of 1.
309   do{
310     if(!n->details->packet_outstanding){
311       do{
312         if(unref){
313           av_packet_unref(n->details->packet);
314         }
315         int averr;
316         if((averr = av_read_frame(n->details->fmtctx, n->details->packet)) < 0){
317           /*if(averr != AVERROR_EOF){
318             fprintf(stderr, "Error reading frame info (%s)\n", av_err2str(averr));
319           }*/
320           return averr2ncerr(averr);
321         }
322         unref = true;
323         if(n->details->packet->stream_index == n->details->sub_stream_index){
324           int result = 0, ret;
325           avsubtitle_free(&n->details->subtitle);
326           ret = avcodec_decode_subtitle2(n->details->subtcodecctx, &n->details->subtitle, &result, n->details->packet);
327           if(ret >= 0 && result){
328             // FIXME?
329           }
330         }
331       }while(n->details->packet->stream_index != n->details->stream_index);
332       n->details->packet_outstanding = true;
333       int averr = avcodec_send_packet(n->details->codecctx, n->details->packet);
334       if(averr < 0){
335         n->details->packet_outstanding = false;
336         av_packet_unref(n->details->packet);
337   //fprintf(stderr, "Error processing AVPacket\n");
338         return averr2ncerr(averr);
339       }
340     }
341     int averr = avcodec_receive_frame(n->details->codecctx, n->details->frame);
342     if(averr >= 0){
343       have_frame = true;
344     }else if(averr < 0){
345       av_packet_unref(n->details->packet);
346       have_frame = false;
347       n->details->packet_outstanding = false;
348       if(averr != AVERROR(EAGAIN)){
349         return averr2ncerr(averr);
350       }
351     }
352 //fprintf(stderr, "Error decoding AVPacket\n");
353   }while(!have_frame);
354 //print_frame_summary(n->details->codecctx, n->details->frame);
355   const AVFrame* f = n->details->frame;
356   n->rowstride = f->linesize[0];
357   n->pixx = n->details->frame->width;
358   n->pixy = n->details->frame->height;
359 //fprintf(stderr, "good decode! %d/%d %d %p\n", n->details->frame->height, n->details->frame->width, n->rowstride, f->data);
360   ncvisual_set_data(n, f->data[0], false);
361   force_rgba(n);
362   return 0;
363 }
364 
365 static ncvisual_details*
ffmpeg_details_init(void)366 ffmpeg_details_init(void){
367   ncvisual_details* deets = malloc(sizeof(*deets));
368   if(deets){
369     memset(deets, 0, sizeof(*deets));
370     deets->stream_index = -1;
371     deets->sub_stream_index = -1;
372     if((deets->frame = av_frame_alloc()) == NULL){
373       free(deets);
374       return NULL;
375     }
376   }
377   return deets;
378 }
379 
380 static ncvisual*
ffmpeg_create()381 ffmpeg_create(){
382   ncvisual* nc = malloc(sizeof(*nc));
383   if(nc){
384     memset(nc, 0, sizeof(*nc));
385     if((nc->details = ffmpeg_details_init()) == NULL){
386       free(nc);
387       return NULL;
388     }
389   }
390   return nc;
391 }
392 
393 static ncvisual*
ffmpeg_from_file(const char * filename)394 ffmpeg_from_file(const char* filename){
395   ncvisual* ncv = ffmpeg_create();
396   if(ncv == NULL){
397     // fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno));
398     return NULL;
399   }
400 //fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame);
401   int averr = avformat_open_input(&ncv->details->fmtctx, filename, NULL, NULL);
402   if(averr < 0){
403 //fprintf(stderr, "Couldn't open %s (%d)\n", filename, averr);
404     goto err;
405   }
406   averr = avformat_find_stream_info(ncv->details->fmtctx, NULL);
407   if(averr < 0){
408 //fprintf(stderr, "Error extracting stream info from %s (%d)\n", filename, averr);
409     goto err;
410   }
411 //av_dump_format(ncv->details->fmtctx, 0, filename, false);
412   if((averr = av_find_best_stream(ncv->details->fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1,
413 #if LIBAVFORMAT_VERSION_MAJOR >= 59
414                                   (const AVCodec**)&ncv->details->subtcodec, 0)) >= 0){
415 #else
416                                   &ncv->details->subtcodec, 0)) >= 0){
417 #endif
418     ncv->details->sub_stream_index = averr;
419     if((ncv->details->subtcodecctx = avcodec_alloc_context3(ncv->details->subtcodec)) == NULL){
420       //fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
421       goto err;
422     }
423     // FIXME do we need avcodec_parameters_to_context() here?
424     if(avcodec_open2(ncv->details->subtcodecctx, ncv->details->subtcodec, NULL) < 0){
425       //fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
426       goto err;
427     }
428   }else{
429     ncv->details->sub_stream_index = -1;
430   }
431 //fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame);
432   if((ncv->details->packet = av_packet_alloc()) == NULL){
433     // fprintf(stderr, "Couldn't allocate packet for %s\n", filename);
434     goto err;
435   }
436   if((averr = av_find_best_stream(ncv->details->fmtctx, AVMEDIA_TYPE_VIDEO, -1, -1,
437 #if LIBAVFORMAT_VERSION_MAJOR >= 59
438                                   (const AVCodec**)&ncv->details->codec, 0)) < 0){
439 #else
440                                   &ncv->details->codec, 0)) < 0){
441 #endif
442     // fprintf(stderr, "Couldn't find visuals in %s (%s)\n", filename, av_err2str(*averr));
443     goto err;
444   }
445   ncv->details->stream_index = averr;
446   if(ncv->details->codec == NULL){
447     //fprintf(stderr, "Couldn't find decoder for %s\n", filename);
448     goto err;
449   }
450   AVStream* st = ncv->details->fmtctx->streams[ncv->details->stream_index];
451   if((ncv->details->codecctx = avcodec_alloc_context3(ncv->details->codec)) == NULL){
452     //fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
453     goto err;
454   }
455   if(avcodec_parameters_to_context(ncv->details->codecctx, st->codecpar) < 0){
456     goto err;
457   }
458   if(avcodec_open2(ncv->details->codecctx, ncv->details->codec, NULL) < 0){
459     //fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
460     goto err;
461   }
462 //fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame);
463   // frame is set up in prep_details(), so that format can be set there, as
464   // is necessary when it is prepared from inputs other than files.
465   if(ffmpeg_decode(ncv)){
466     goto err;
467   }
468   return ncv;
469 
470 err:
471   ncvisual_destroy(ncv);
472   return NULL;
473 }
474 
475 // iterate over the decoded frames, calling streamer() with curry for each.
476 // frames carry a presentation time relative to the beginning, so we get an
477 // initial timestamp, and check each frame against the elapsed time to sync
478 // up playback.
479 static int
480 ffmpeg_stream(notcurses* nc, ncvisual* ncv, float timescale,
481               ncstreamcb streamer, const struct ncvisual_options* vopts,
482               void* curry){
483   int frame = 1;
484   struct timespec begin; // time we started
485   clock_gettime(CLOCK_MONOTONIC, &begin);
486   uint64_t nsbegin = timespec_to_ns(&begin);
487   //bool usets = false;
488   // each frame has a pkt_duration in milliseconds. keep the aggregate, in case
489   // we don't have PTS available.
490   uint64_t sum_duration = 0;
491   ncplane* newn = NULL;
492   struct ncvisual_options activevopts;
493   memcpy(&activevopts, vopts, sizeof(*vopts));
494   int ncerr;
495   do{
496     // codecctx seems to be off by a factor of 2 regularly. instead, go with
497     // the time_base from the avformatctx. except ts isn't properly reset for
498     // all media when we loop =[. we seem to be accurate enough now with the
499     // tbase/ppd. see https://github.com/dankamongmen/notcurses/issues/1352.
500     double tbase = av_q2d(ncv->details->fmtctx->streams[ncv->details->stream_index]->time_base);
501     if(isnan(tbase)){
502       tbase = 0;
503     }
504     if(activevopts.n){
505       ncplane_erase(activevopts.n); // new frame could be partially transparent
506     }
507     // decay the blitter explicitly, so that the callback knows the blitter it
508     // was actually rendered with. basically just need rgba_blitter(), but
509     // that's not exported.
510     ncvgeom geom;
511     ncvisual_geom(nc, ncv, &activevopts, &geom);
512     activevopts.blitter = geom.blitter;
513     if((newn = ncvisual_blit(nc, ncv, &activevopts)) == NULL){
514       if(activevopts.n != vopts->n){
515         ncplane_destroy(activevopts.n);
516       }
517       return -1;
518     }
519     if(activevopts.n != newn){
520       activevopts.n = newn;
521     }
522     ++frame;
523     uint64_t duration = ncv->details->frame->pkt_duration * tbase * NANOSECS_IN_SEC;
524     double schedns = nsbegin;
525     sum_duration += (duration * timescale);
526     schedns += sum_duration;
527     struct timespec abstime;
528     ns_to_timespec(schedns, &abstime);
529     int r;
530     if(streamer){
531       r = streamer(ncv, &activevopts, &abstime, curry);
532     }else{
533       r = ncvisual_simple_streamer(ncv, &activevopts, &abstime, curry);
534     }
535     if(r){
536       if(activevopts.n != vopts->n){
537         ncplane_destroy(activevopts.n);
538       }
539       return r;
540     }
541   }while((ncerr = ffmpeg_decode(ncv)) == 0);
542   if(activevopts.n != vopts->n){
543     ncplane_destroy(activevopts.n);
544   }
545   if(ncerr == 1){ // 1 indicates reaching EOF
546     ncerr = 0;
547   }
548   return ncerr;
549 }
550 
551 static int
552 ffmpeg_decode_loop(ncvisual* ncv){
553   int r = ffmpeg_decode(ncv);
554   if(r == 1){
555     if(av_seek_frame(ncv->details->fmtctx, ncv->details->stream_index, 0, AVSEEK_FLAG_FRAME) < 0){
556       // FIXME log error
557       return -1;
558     }
559     if(ffmpeg_decode(ncv) < 0){
560       return -1;
561     }
562   }
563   return r;
564 }
565 
566 // do a resize *without* updating the ncvisual structure. if the target
567 // parameters are already matched, the existing data will be returned.
568 // otherwise, a scaled copy will be returned. they can be differentiated by
569 // comparing the result against ncv->data.
570 static uint32_t*
571 ffmpeg_resize_internal(const ncvisual* ncv, int rows, int* stride, int cols,
572                        const blitterargs* bargs){
573   const AVFrame* inframe = ncv->details->frame;
574 //print_frame_summary(NULL, inframe);
575   const int targformat = AV_PIX_FMT_RGBA;
576 //fprintf(stderr, "got format: %d (%d/%d) want format: %d (%d/%d)\n", inframe->format, inframe->height, inframe->width, targformat, rows, cols);
577   // FIXME need account for beg{y,x} here, no? what if no inframe?
578   if(!inframe || (cols == inframe->width && rows == inframe->height && inframe->format == targformat)){
579     // no change necessary. return original data -- we don't duplicate.
580     *stride = ncv->rowstride;
581     return ncv->data;
582   }
583   const int srclenx = bargs->lenx ? bargs->lenx : inframe->width;
584   const int srcleny = bargs->leny ? bargs->leny : inframe->height;
585 //fprintf(stderr, "src %d/%d -> targ %d/%d ctx: %p\n", srcleny, srclenx, rows, cols, ncv->details->swsctx);
586   ncv->details->swsctx = sws_getCachedContext(ncv->details->swsctx,
587                                               srclenx, srcleny,
588                                               inframe->format,
589                                               cols, rows, targformat,
590                                               SWS_LANCZOS, NULL, NULL, NULL);
591   if(ncv->details->swsctx == NULL){
592 //fprintf(stderr, "Error retrieving details->swsctx\n");
593     return NULL;
594   }
595   // necessitated by ffmpeg AVPicture API
596   uint8_t* dptrs[4];
597   int dlinesizes[4];
598   int size = av_image_alloc(dptrs, dlinesizes, cols, rows, targformat, IMGALLOCALIGN);
599   if(size < 0){
600 //fprintf(stderr, "Error allocating visual data (%d X %d)\n", sframe->height, sframe->width);
601     return NULL;
602   }
603 //fprintf(stderr, "INFRAME DAA: %p SDATA: %p FDATA: %p to %d/%d\n", inframe->data[0], sframe->data[0], ncv->details->frame->data[0], sframe->height, sframe->width);
604   const uint8_t* data[4] = { (uint8_t*)ncv->data, };
605   int height = sws_scale(ncv->details->swsctx, data,
606                          inframe->linesize, 0, srcleny, dptrs, dlinesizes);
607   if(height < 0){
608 //fprintf(stderr, "Error applying scaling (%d X %d)\n", inframe->height, inframe->width);
609     av_freep(&dptrs[0]);
610     return NULL;
611   }
612 //fprintf(stderr, "scaled %d/%d to %d/%d\n", ncv->pixy, ncv->pixx, rows, cols);
613   *stride = dlinesizes[0]; // FIXME check for others?
614   return (uint32_t*)dptrs[0];
615 }
616 
617 // resize frame, converting to RGBA (if necessary) along the way
618 static int
619 ffmpeg_resize(ncvisual* n, unsigned rows, unsigned cols){
620   struct blitterargs bargs = {};
621   int stride;
622   void* data = ffmpeg_resize_internal(n, rows, &stride, cols, &bargs);
623   if(data == n->data){ // no change, return
624     return 0;
625   }
626   if(data == NULL){
627     return -1;
628   }
629   AVFrame* inf = n->details->frame;
630 //fprintf(stderr, "WHN NCV: %d/%d %p\n", inf->width, inf->height, n->data);
631   inf->width = cols;
632   inf->height = rows;
633   inf->linesize[0] = stride;
634   n->rowstride = stride;
635   n->pixy = rows;
636   n->pixx = cols;
637   ncvisual_set_data(n, data, true);
638 //fprintf(stderr, "SIZE SCALED: %d %d (%u)\n", n->details->frame->height, n->details->frame->width, n->details->frame->linesize[0]);
639   return 0;
640 }
641 
642 // rows/cols: scaled output geometry (pixels)
643 static int
644 ffmpeg_blit(ncvisual* ncv, unsigned rows, unsigned cols, ncplane* n,
645             const struct blitset* bset, const blitterargs* bargs){
646   void* data;
647   int stride = 0;
648   data = ffmpeg_resize_internal(ncv, rows, &stride, cols, bargs);
649   if(data == NULL){
650     return -1;
651   }
652 //fprintf(stderr, "WHN NCV: bargslen: %d/%d targ: %d/%d\n", bargs->leny, bargs->lenx, rows, cols);
653   int ret = 0;
654   if(rgba_blit_dispatch(n, bset, stride, data, rows, cols, bargs) < 0){
655     ret = -1;
656   }
657   if(data != ncv->data){
658     av_freep(&data); // &dptrs[0]
659   }
660   return ret;
661 }
662 
663 static void
664 ffmpeg_details_seed(ncvisual* ncv){
665   av_frame_unref(ncv->details->frame);
666   memset(ncv->details->frame, 0, sizeof(*ncv->details->frame));
667   ncv->details->frame->linesize[0] = ncv->rowstride;
668   ncv->details->frame->width = ncv->pixx;
669   ncv->details->frame->height = ncv->pixy;
670   ncv->details->frame->format = AV_PIX_FMT_RGBA;
671 }
672 
673 static int
674 ffmpeg_log_level(int level){
675   switch(level){
676     case NCLOGLEVEL_SILENT: return AV_LOG_QUIET;
677     case NCLOGLEVEL_PANIC: return AV_LOG_PANIC;
678     case NCLOGLEVEL_FATAL: return AV_LOG_FATAL;
679     case NCLOGLEVEL_ERROR: return AV_LOG_ERROR;
680     case NCLOGLEVEL_WARNING: return AV_LOG_WARNING;
681     case NCLOGLEVEL_INFO: return AV_LOG_INFO;
682     case NCLOGLEVEL_VERBOSE: return AV_LOG_VERBOSE;
683     case NCLOGLEVEL_DEBUG: return AV_LOG_DEBUG;
684     case NCLOGLEVEL_TRACE: return AV_LOG_TRACE;
685     default: break;
686   }
687   fprintf(stderr, "Invalid log level: %d\n", level);
688   return AV_LOG_TRACE;
689 }
690 
691 static int
692 ffmpeg_init(int logl){
693   av_log_set_level(ffmpeg_log_level(logl));
694   // FIXME could also use av_log_set_callback() and capture the message...
695   return 0;
696 }
697 
698 static void
699 ffmpeg_printbanner(fbuf* f){
700   fbuf_printf(f, "avformat %u.%u.%u avutil %u.%u.%u swscale %u.%u.%u avcodec %u.%u.%u" NL,
701               LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO,
702               LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO,
703               LIBSWSCALE_VERSION_MAJOR, LIBSWSCALE_VERSION_MINOR, LIBSWSCALE_VERSION_MICRO,
704               LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO);
705 }
706 
707 static void
708 ffmpeg_details_destroy(ncvisual_details* deets){
709   avcodec_close(deets->codecctx);
710   avcodec_free_context(&deets->subtcodecctx);
711   avcodec_free_context(&deets->codecctx);
712   av_frame_free(&deets->frame);
713   sws_freeContext(deets->rgbactx);
714   sws_freeContext(deets->swsctx);
715   av_packet_free(&deets->packet);
716   avformat_close_input(&deets->fmtctx);
717   avsubtitle_free(&deets->subtitle);
718   free(deets);
719 }
720 
721 static void
722 ffmpeg_destroy(ncvisual* ncv){
723   if(ncv){
724     ffmpeg_details_destroy(ncv->details);
725     if(ncv->owndata){
726       free(ncv->data);
727     }
728     free(ncv);
729   }
730 }
731 
732 ncvisual_implementation local_visual_implementation = {
733   .visual_init = ffmpeg_init,
734   .visual_printbanner = ffmpeg_printbanner,
735   .visual_blit = ffmpeg_blit,
736   .visual_create = ffmpeg_create,
737   .visual_from_file = ffmpeg_from_file,
738   .visual_details_seed = ffmpeg_details_seed,
739   .visual_decode = ffmpeg_decode,
740   .visual_decode_loop = ffmpeg_decode_loop,
741   .visual_stream = ffmpeg_stream,
742   .visual_subtitle = ffmpeg_subtitle,
743   .visual_resize = ffmpeg_resize,
744   .visual_destroy = ffmpeg_destroy,
745   .rowalign = 64, // ffmpeg wants multiples of IMGALIGN (64)
746   .canopen_images = true,
747   .canopen_videos = true,
748 };
749 
750 #endif
751