diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index 9e09cb3..f4da60f 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -3406,6 +3406,10 @@ API int ncvisual_geom(const struct notcurses* nc, const struct ncvisual* n, const struct ncvisual_options* vopts, ncvgeom* geom) __attribute__ ((nonnull (4))); + +API uint64_t ncvisual_get_duration(const struct ncvisual* n) + __attribute__ ((nonnull (1))); + // Destroy an ncvisual. Rendered elements will not be disrupted, but the visual // can be neither decoded nor rendered any further. API void ncvisual_destroy(struct ncvisual* ncv); diff --git a/src/lib/visual-details.h b/src/lib/visual-details.h index e9347e3..6852b76 100644 --- a/src/lib/visual-details.h +++ b/src/lib/visual-details.h @@ -25,6 +25,7 @@ typedef struct ncvisual { unsigned pixx, pixy; // pixel geometry, *not* cell geometry // lines are sometimes padded. this many true bytes per row in data. unsigned rowstride; + uint64_t duration; bool owndata; // we own data iff owndata == true } ncvisual; diff --git a/src/lib/visual.c b/src/lib/visual.c index 047b68e..25e8422 100644 --- a/src/lib/visual.c +++ b/src/lib/visual.c @@ -458,6 +458,10 @@ int ncvisual_geom(const notcurses* nc, const ncvisual* n, &disppxy, &disppxx, &outy, &outx, &placey, &placex); } +uint64_t ncvisual_get_duration(const ncvisual* n){ + return n->duration; +} + void* rgb_loose_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha){ if(*rowstride % 4){ // must be a multiple of 4 bytes return NULL; diff --git a/src/media/ffmpeg.c b/src/media/ffmpeg.c index 655f269..d2bf399 100644 --- a/src/media/ffmpeg.c +++ b/src/media/ffmpeg.c @@ -33,6 +33,7 @@ typedef struct ncvisual_details { struct SwsContext* swsctx; struct SwsContext* rgbactx; AVSubtitle subtitle; + AVRational timebase; int stream_index; // match against this following av_read_frame() int sub_stream_index; // subtitle stream index, can be < 0 if no subtitles bool packet_outstanding; @@ -224,9 +225,7 @@ struct ncplane* ffmpeg_subtitle(ncplane* parent, const ncvisual* ncv){ static int averr2ncerr(int averr){ - if(averr == AVERROR_EOF){ - return 1; - } + (void)averr; // FIXME need to map averror codes to ncerrors //fprintf(stderr, "AVERR: %d/%x %d/%x\n", averr, averr, -averr, -averr); return -1; @@ -326,6 +325,11 @@ ffmpeg_decode(ncvisual* n){ /*if(averr != AVERROR_EOF){ fprintf(stderr, "Error reading frame info (%s)\n", av_err2str(averr)); }*/ + if(averr == AVERROR_EOF){ + // Start flush. + unref = false; + break; + } return averr2ncerr(averr); } unref = true; @@ -339,10 +343,10 @@ ffmpeg_decode(ncvisual* n){ } }while(n->details->packet->stream_index != n->details->stream_index); n->details->packet_outstanding = true; - int averr = avcodec_send_packet(n->details->codecctx, n->details->packet); + int averr = avcodec_send_packet(n->details->codecctx, unref ? n->details->packet : NULL); if(averr < 0){ - n->details->packet_outstanding = false; av_packet_unref(n->details->packet); + n->details->packet_outstanding = false; //fprintf(stderr, "Error processing AVPacket\n"); return averr2ncerr(averr); } @@ -350,10 +354,16 @@ ffmpeg_decode(ncvisual* n){ int averr = avcodec_receive_frame(n->details->codecctx, n->details->frame); if(averr >= 0){ have_frame = true; + }else if(averr == AVERROR_EOF){ + n->details->packet_outstanding = false; + // Where EOF gets returned during normal operation. + return 1; }else if(averr < 0){ - av_packet_unref(n->details->packet); - have_frame = false; + if(unref){ + av_packet_unref(n->details->packet); + } n->details->packet_outstanding = false; + have_frame = false; if(averr != AVERROR(EAGAIN)){ return averr2ncerr(averr); } @@ -365,6 +375,8 @@ ffmpeg_decode(ncvisual* n){ n->rowstride = f->linesize[0]; n->pixx = n->details->frame->width; n->pixy = n->details->frame->height; + const uint64_t pktduration = ffmpeg_pkt_duration(f); + n->duration = av_rescale_q(pktduration, n->details->timebase, AV_TIME_BASE_Q); //fprintf(stderr, "good decode! %d/%d %d %p\n", n->details->frame->height, n->details->frame->width, n->rowstride, f->data); ncvisual_set_data(n, f->data[0], false); force_rgba(n); @@ -457,6 +469,7 @@ ffmpeg_from_file(const char* filename){ goto err; } AVStream* st = ncv->details->fmtctx->streams[ncv->details->stream_index]; + ncv->details->timebase = st->time_base; if((ncv->details->codecctx = avcodec_alloc_context3(ncv->details->codec)) == NULL){ //fprintf(stderr, "Couldn't allocate decoder for %s\n", filename); goto err; @@ -501,14 +514,6 @@ ffmpeg_stream(notcurses* nc, ncvisual* ncv, float timescale, memcpy(&activevopts, vopts, sizeof(*vopts)); int ncerr; do{ - // codecctx seems to be off by a factor of 2 regularly. instead, go with - // the time_base from the avformatctx. except ts isn't properly reset for - // all media when we loop =[. we seem to be accurate enough now with the - // tbase/ppd. see https://github.com/dankamongmen/notcurses/issues/1352. - double tbase = av_q2d(ncv->details->fmtctx->streams[ncv->details->stream_index]->time_base); - if(isnan(tbase)){ - tbase = 0; - } if(activevopts.n){ ncplane_erase(activevopts.n); // new frame could be partially transparent } @@ -529,7 +534,7 @@ ffmpeg_stream(notcurses* nc, ncvisual* ncv, float timescale, } // display duration in units of time_base const uint64_t pktduration = ffmpeg_pkt_duration(ncv->details->frame); - uint64_t duration = pktduration * tbase * NANOSECS_IN_SEC; + uint64_t duration = av_rescale_q(pktduration, ncv->details->timebase, (AVRational){1, NANOSECS_IN_SEC}); double schedns = nsbegin; sum_duration += (duration * timescale); schedns += sum_duration; @@ -561,11 +566,13 @@ static int ffmpeg_decode_loop(ncvisual* ncv){ int r = ffmpeg_decode(ncv); if(r == 1){ - if(av_seek_frame(ncv->details->fmtctx, ncv->details->stream_index, 0, AVSEEK_FLAG_FRAME) < 0){ + avformat_flush(ncv->details->fmtctx); + if(avformat_seek_file(ncv->details->fmtctx, ncv->details->stream_index, 0, 0, 0, AVSEEK_FLAG_FRAME) < 0){ // FIXME log error return -1; } - if(ffmpeg_decode(ncv) < 0){ + avcodec_flush_buffers(ncv->details->codecctx); + if(ffmpeg_decode(ncv) != 0){ return -1; } }