X-Git-Url: https://git.cinelerra-gg.org/git/?p=goodguy%2Fcinelerra.git;a=blobdiff_plain;f=cinelerra-5.1%2Fcinelerra%2Fffmpeg.C;h=493be6abeace4e55e9eac4177964649a63c6fc2e;hp=58d280c00edf60315dd93d383e05196d9ee04a49;hb=refs%2Fheads%2Fmaster;hpb=fbdd13b462256ed4f3b35dc114385fe0b0de0dcd diff --git a/cinelerra-5.1/cinelerra/ffmpeg.C b/cinelerra-5.1/cinelerra/ffmpeg.C index 58d280c0..6685f468 100644 --- a/cinelerra-5.1/cinelerra/ffmpeg.C +++ b/cinelerra-5.1/cinelerra/ffmpeg.C @@ -1,3 +1,22 @@ +/* + * CINELERRA + * Copyright (C) 2012-2014 Paolo Rampino + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ #include #include @@ -29,7 +48,9 @@ #include "ffmpeg.h" #include "indexfile.h" #include "interlacemodes.h" +#ifdef HAVE_DV #include "libdv.h" +#endif #include "libmjpeg.h" #include "mainerror.h" #include "mwindow.h" @@ -263,12 +284,15 @@ FFStream::FFStream(FFMPEG *ffmpeg, AVStream *st, int fidx) fmt_ctx = 0; avctx = 0; filter_graph = 0; + filt_ctx = 0; + filt_id = 0; buffersrc_ctx = 0; buffersink_ctx = 0; frm_count = 0; nudge = AV_NOPTS_VALUE; seek_pos = curr_pos = 0; - seeked = 1; eof = 0; + seeking = 0; seeked = 1; + eof = 0; reading = writing = 0; hw_pixfmt = AV_PIX_FMT_NONE; hw_device_ctx = 0; @@ -285,6 +309,7 @@ FFStream::FFStream(FFMPEG *ffmpeg, AVStream *st, int fidx) FFStream::~FFStream() { + frm_lock->lock("FFStream::~FFStream"); if( reading > 0 || writing > 0 ) avcodec_close(avctx); if( avctx ) avcodec_free_context(&avctx); if( fmt_ctx ) avformat_close_input(&fmt_ctx); @@ -295,6 +320,7 @@ FFStream::~FFStream() if( frame ) av_frame_free(&frame); if( fframe ) av_frame_free(&fframe); if( probe_frame ) av_frame_free(&probe_frame); + frm_lock->unlock(); delete frm_lock; if( stats_fp ) fclose(stats_fp); if( stats_in ) av_freep(&stats_in); @@ -373,8 +399,11 @@ AVHWDeviceType FFStream::decode_hw_activate() { return AV_HWDEVICE_TYPE_NONE; } - +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) +int FFStream::decode_hw_format(const AVCodec *decoder, AVHWDeviceType type) +#else int FFStream::decode_hw_format(AVCodec *decoder, AVHWDeviceType type) +#endif { return 0; } @@ -399,7 +428,11 @@ int FFStream::decode_activate() } while( ret >= 0 && st != 0 && !reading ) { AVCodecID codec_id = st->codecpar->codec_id; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) + const AVCodec *decoder = 0; +#else AVCodec *decoder = 0; +#endif if( is_video() ) { if( ffmpeg->opt_video_decoder ) decoder = avcodec_find_decoder_by_name(ffmpeg->opt_video_decoder); @@ -428,14 +461,14 @@ int FFStream::decode_activate() avctx->thread_count = ffmpeg->ff_cpus(); ret = avcodec_open2(avctx, decoder, &copts); } + AVFrame *hw_frame = 0; if( ret >= 0 && hw_type != AV_HWDEVICE_TYPE_NONE ) { - AVFrame *frame = av_frame_alloc(); - if( !frame ) { + if( !(hw_frame=av_frame_alloc()) ) { fprintf(stderr, "FFStream::decode_activate: av_frame_alloc failed\n"); ret = AVERROR(ENOMEM); } if( ret >= 0 ) - ret = decode(frame); + ret = decode(hw_frame); } if( ret < 0 && hw_type != AV_HWDEVICE_TYPE_NONE ) { ff_err(ret, "HW device init failed, using SW decode.\nfile:%s\n", @@ -444,7 +477,7 @@ int FFStream::decode_activate() avcodec_free_context(&avctx); av_buffer_unref(&hw_device_ctx); hw_device_ctx = 0; - av_frame_free(&frame); + av_frame_free(&hw_frame); hw_type = AV_HWDEVICE_TYPE_NONE; int flags = AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY; int idx = st->index; @@ -454,7 +487,7 @@ int FFStream::decode_activate() ret = 0; continue; } - probe_frame = frame; + probe_frame = hw_frame; if( ret >= 0 ) reading = 1; else @@ -491,7 +524,7 @@ int FFStream::decode(AVFrame *frame) } int ret = 0; int retries = MAX_RETRY; - + frm_lock->lock("FFStream::decode"); while( ret >= 0 && !flushed && --retries >= 0 ) { if( need_packet ) { if( (ret=read_packet()) < 0 ) break; @@ -514,6 +547,7 @@ int FFStream::decode(AVFrame *frame) flushed = st_eof(); } } + frm_lock->unlock(); if( retries < 0 ) { fprintf(stderr, "FFStream::decode: Retry limit\n"); @@ -570,6 +604,10 @@ int FFStream::write_packet(FFPacket &pkt) ret = av_interleaved_write_frame(ffmpeg->fmt_ctx, pkt); } else { + bsfc->time_base_in = st->time_base; + avcodec_parameters_copy(bsfc->par_in, st->codecpar); + av_bsf_init(bsfc); + ret = av_bsf_send_packet(bsfc, pkt); while( ret >= 0 ) { FFPacket bs; @@ -578,6 +616,9 @@ int FFStream::write_packet(FFPacket &pkt) if( ret == AVERROR_EOF ) return -1; break; } + //printf(" filter name %s \n", bsfc->filter[0].name); + //avcodec_parameters_copy(ffmpeg->fmt_ctx->streams[0]->codecpar, bsfc->par_out); + //avcodec_parameters_copy(st->codecpar, bsfc->par_out); av_packet_rescale_ts(bs, avctx->time_base, st->time_base); bs->stream_index = st->index; ret = av_interleaved_write_frame(ffmpeg->fmt_ctx, bs); @@ -597,10 +638,11 @@ int FFStream::encode_frame(AVFrame *frame) ret = avcodec_send_frame(avctx, frame); if( !ret && frame ) return pkts; if( ret < 0 && ret != AVERROR(EAGAIN) ) break; + if ( ret == AVERROR(EAGAIN) && !frame ) continue; FFPacket opkt; ret = avcodec_receive_packet(avctx, opkt); - if( !frame && ret == AVERROR_EOF ) return pkts; - if( ret < 0 ) break; + if( !frame && (ret == AVERROR_EOF || ret == AVERROR(EAGAIN) )) return pkts; + //if( ret < 0 ) break; ret = write_packet(opkt); if( ret < 0 ) break; ++pkts; @@ -709,13 +751,23 @@ int FFStream::seek(int64_t no, double rate) } } if( pos == curr_pos ) return 0; + seeking = -1; double secs = pos < 0 ? 0. : pos / rate; AVRational time_base = st->time_base; int64_t tstmp = time_base.num > 0 ? secs * time_base.den/time_base.num : 0; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) + int nb_index_entries = avformat_index_get_entries_count(st); +#endif if( !tstmp ) { +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) + if( nb_index_entries > 0 ) tstmp = (avformat_index_get_entry(st, 0))->timestamp; +#else if( st->nb_index_entries > 0 ) tstmp = st->index_entries[0].timestamp; +#endif else if( st->start_time != AV_NOPTS_VALUE ) tstmp = st->start_time; +#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(58,134,100) else if( st->first_dts != AV_NOPTS_VALUE ) tstmp = st->first_dts; +#endif else tstmp = INT64_MIN+1; } else if( nudge != AV_NOPTS_VALUE ) tstmp += nudge; @@ -726,6 +778,7 @@ int FFStream::seek(int64_t no, double rate) tstmp = av_rescale_q(tstmp, time_base, AV_TIME_BASE_Q); idx = -1; #endif + frm_lock->lock("FFStream::seek"); av_frame_free(&probe_frame); avcodec_flush_buffers(avctx); avformat_flush(fmt_ctx); @@ -758,7 +811,9 @@ int FFStream::seek(int64_t no, double rate) if( pkt_ts >= tstmp ) break; } if( retry < 0 ) { - fprintf(stderr,"FFStream::seek: retry limit, pos=%jd tstmp=%jd\n",pos,tstmp); + ff_err(AVERROR(EIO), "FFStream::seek: %s\n" + " retry limit, pos=%jd tstmp=%jd, ", + ffmpeg->fmt_ctx->url, pos, tstmp); ret = -1; } if( ret < 0 ) break; @@ -772,6 +827,7 @@ int FFStream::seek(int64_t no, double rate) break; } } + frm_lock->unlock(); if( ret < 0 ) { printf("** seek fail %jd, %jd\n", pos, tstmp); seeked = need_packet = 0; @@ -1044,6 +1100,7 @@ FFVideoStream::FFVideoStream(FFMPEG *ffmpeg, AVStream *strm, int idx, int fidx) { this->idx = idx; width = height = 0; + transpose = 0; frame_rate = 0; aspect_ratio = 0; length = 0; @@ -1051,10 +1108,12 @@ FFVideoStream::FFVideoStream(FFMPEG *ffmpeg, AVStream *strm, int idx, int fidx) top_field_first = 0; color_space = -1; color_range = -1; + fconvert_ctx = 0; } FFVideoStream::~FFVideoStream() { + if( fconvert_ctx ) sws_freeContext(fconvert_ctx); } AVHWDeviceType FFVideoStream::decode_hw_activate() @@ -1076,8 +1135,11 @@ AVHWDeviceType FFVideoStream::decode_hw_activate() } return type; } - +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) +int FFVideoStream::decode_hw_format(const AVCodec *decoder, AVHWDeviceType type) +#else int FFVideoStream::decode_hw_format(AVCodec *decoder, AVHWDeviceType type) +#endif { int ret = 0; hw_pix_fmt = AV_PIX_FMT_NONE; @@ -1098,7 +1160,14 @@ int FFVideoStream::decode_hw_format(AVCodec *decoder, AVHWDeviceType type) if( hw_pix_fmt >= 0 ) { hw_pixfmt = hw_pix_fmt; avctx->get_format = get_hw_format; + const char *drm_node = getenv("CIN_DRM_DEC"); + if(drm_node && type==AV_HWDEVICE_TYPE_VAAPI) { + ret = av_hwdevice_ctx_create(&hw_device_ctx, type, drm_node, 0, 0); + } + else { ret = av_hwdevice_ctx_create(&hw_device_ctx, type, 0, 0, 0); + } + if( ret >= 0 ) { avctx->hw_device_ctx = av_buffer_ref(hw_device_ctx); ret = 1; @@ -1114,6 +1183,7 @@ int FFVideoStream::decode_hw_format(AVCodec *decoder, AVHWDeviceType type) AVHWDeviceType FFVideoStream::encode_hw_activate(const char *hw_dev) { + const char *drm_node_enc = getenv("CIN_DRM_ENC"); AVBufferRef *hw_device_ctx = 0; AVBufferRef *hw_frames_ref = 0; AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE; @@ -1125,7 +1195,12 @@ AVHWDeviceType FFVideoStream::encode_hw_activate(const char *hw_dev) } } if( type != AV_HWDEVICE_TYPE_NONE ) { - int ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, 0, 0, 0); + int ret = 0; + if (drm_node_enc) { + ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, drm_node_enc, 0, 0); + } else { + ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, 0, 0, 0); + } if( ret < 0 ) { ff_err(ret, "Failed to create a HW device.\n"); type = AV_HWDEVICE_TYPE_NONE; @@ -1199,6 +1274,40 @@ int FFVideoStream::decode_frame(AVFrame *frame) return 1; } +int FFVideoStream::probe(int64_t pos) +{ + int ret = video_seek(pos); + if( ret < 0 ) return -1; + if( !frame && !(frame=av_frame_alloc()) ) { + fprintf(stderr, "FFVideoStream::probe: av_frame_alloc failed\n"); + return -1; + } + + if (ffmpeg->interlace_from_codec) return 1; + + ret = read_frame(frame); + if( ret > 0 ) { + //printf("codec interlace: %i \n",frame->interlaced_frame); + //printf("codec tff: %i \n",frame->top_field_first); + + if (!frame->interlaced_frame) + ffmpeg->interlace_from_codec = AV_FIELD_PROGRESSIVE; + if ((frame->interlaced_frame) && (frame->top_field_first)) + ffmpeg->interlace_from_codec = AV_FIELD_TT; + if ((frame->interlaced_frame) && (!frame->top_field_first)) + ffmpeg->interlace_from_codec = AV_FIELD_BB; + //printf("Interlace mode from codec: %i\n", ffmpeg->interlace_from_codec); + + } + + if( frame->format == AV_PIX_FMT_NONE || frame->width <= 0 || frame->height <= 0 ) + ret = -1; + + ret = ret > 0 ? 1 : ret < 0 ? -1 : 0; + av_frame_free(&frame); + return ret; +} + int FFVideoStream::load(VFrame *vframe, int64_t pos) { int ret = video_seek(pos); @@ -1207,11 +1316,39 @@ int FFVideoStream::load(VFrame *vframe, int64_t pos) fprintf(stderr, "FFVideoStream::load: av_frame_alloc failed\n"); return -1; } + + int i = MAX_RETRY + pos - curr_pos; + int64_t cache_start = 0; while( ret>=0 && !flushed && curr_pos<=pos && --i>=0 ) { ret = read_frame(frame); - if( ret > 0 ) ++curr_pos; + if( ret > 0 ) { + if( frame->key_frame && seeking < 0 ) { + int use_cache = ffmpeg->get_use_cache(); + if( use_cache < 0 ) { +// for reverse read, reload file frame_cache from keyframe to pos + ffmpeg->purge_cache(); + int count = preferences->cache_size / + vframe->get_data_size() / 2; // try to burn only 1/2 of cache + cache_start = pos - count + 1; + seeking = 1; + } + else + seeking = 0; + } + if( seeking > 0 && curr_pos >= cache_start && curr_pos < pos ) { + int vw =vframe->get_w(), vh = vframe->get_h(); + int vcolor_model = vframe->get_color_model(); +// do not use shm here, puts too much pressure on 32bit systems + VFrame *cache_frame = new VFrame(vw, vh, vcolor_model, 0); + ret = convert_cmodel(cache_frame, frame); + if( ret > 0 ) + ffmpeg->put_cache_frame(cache_frame, curr_pos); + } + ++curr_pos; + } } + seeking = 0; if( frame->format == AV_PIX_FMT_NONE || frame->width <= 0 || frame->height <= 0 ) ret = -1; if( ret >= 0 ) { @@ -1253,6 +1390,78 @@ int FFVideoStream::init_frame(AVFrame *picture) return ret; } +int FFVideoStream::convert_hw_frame(AVFrame *ifrm, AVFrame *ofrm) +{ + AVPixelFormat ifmt = (AVPixelFormat)ifrm->format; + AVPixelFormat ofmt = (AVPixelFormat)st->codecpar->format; + ofrm->width = ifrm->width; + ofrm->height = ifrm->height; + ofrm->format = ofmt; + int ret = av_frame_get_buffer(ofrm, 32); + if( ret < 0 ) { + ff_err(ret, "FFVideoStream::convert_hw_frame:" + " av_frame_get_buffer failed\n"); + return -1; + } + fconvert_ctx = sws_getCachedContext(fconvert_ctx, + ifrm->width, ifrm->height, ifmt, + ofrm->width, ofrm->height, ofmt, + SWS_POINT, NULL, NULL, NULL); + if( !fconvert_ctx ) { + ff_err(AVERROR(EINVAL), "FFVideoStream::convert_hw_frame:" + " sws_getCachedContext() failed\n"); + return -1; + } + int codec_range = st->codecpar->color_range; + int codec_space = st->codecpar->color_space; + const int *codec_table = sws_getCoefficients(codec_space); + int *inv_table, *table, src_range, dst_range; + int brightness, contrast, saturation; + if( !sws_getColorspaceDetails(fconvert_ctx, + &inv_table, &src_range, &table, &dst_range, + &brightness, &contrast, &saturation) ) { + if( src_range != codec_range || dst_range != codec_range || + inv_table != codec_table || table != codec_table ) + sws_setColorspaceDetails(fconvert_ctx, + codec_table, codec_range, codec_table, codec_range, + brightness, contrast, saturation); + } + ret = sws_scale(fconvert_ctx, + ifrm->data, ifrm->linesize, 0, ifrm->height, + ofrm->data, ofrm->linesize); + if( ret < 0 ) { + ff_err(ret, "FFVideoStream::convert_hw_frame:" + " sws_scale() failed\nfile: %s\n", + ffmpeg->fmt_ctx->url); + return -1; + } + return 0; +} + +int FFVideoStream::load_filter(AVFrame *frame) +{ + AVPixelFormat pix_fmt = (AVPixelFormat)frame->format; + if( pix_fmt == hw_pixfmt ) { + AVFrame *hw_frame = this->frame; + av_frame_unref(hw_frame); + int ret = av_hwframe_transfer_data(hw_frame, frame, 0); + if( ret < 0 ) { + eprintf(_("Error retrieving data from GPU to CPU\nfile: %s\n"), + ffmpeg->fmt_ctx->url); + return -1; + } + av_frame_unref(frame); + ret = convert_hw_frame(hw_frame, frame); + if( ret < 0 ) { + eprintf(_("Error converting data from GPU to CPU\nfile: %s\n"), + ffmpeg->fmt_ctx->url); + return -1; + } + av_frame_unref(hw_frame); + } + return FFStream::load_filter(frame); +} + int FFVideoStream::encode(VFrame *vframe) { if( encode_activate() <= 0 ) return -1; @@ -1279,6 +1488,7 @@ int FFVideoStream::encode(VFrame *vframe) int FFVideoStream::drain() { + return 0; } @@ -1441,9 +1651,11 @@ int FFVideoConvert::convert_picture_vframe(VFrame *frame, AVFrame *ip, AVFrame * } int color_space = SWS_CS_ITU601; switch( preferences->yuv_color_space ) { - case BC_COLORS_BT601: color_space = SWS_CS_ITU601; break; + case BC_COLORS_BT601_PAL: color_space = SWS_CS_ITU601; break; + case BC_COLORS_BT601_NTSC: color_space = SWS_CS_SMPTE170M; break; case BC_COLORS_BT709: color_space = SWS_CS_ITU709; break; - case BC_COLORS_BT2020: color_space = SWS_CS_BT2020; break; + case BC_COLORS_BT2020_NCL: + case BC_COLORS_BT2020_CL: color_space = SWS_CS_BT2020; break; } const int *color_table = sws_getCoefficients(color_space); @@ -1570,9 +1782,11 @@ int FFVideoConvert::convert_vframe_picture(VFrame *frame, AVFrame *op, AVFrame * } int color_space = SWS_CS_ITU601; switch( preferences->yuv_color_space ) { - case BC_COLORS_BT601: color_space = SWS_CS_ITU601; break; + case BC_COLORS_BT601_PAL: color_space = SWS_CS_ITU601; break; + case BC_COLORS_BT601_NTSC: color_space = SWS_CS_SMPTE170M; break; case BC_COLORS_BT709: color_space = SWS_CS_ITU709; break; - case BC_COLORS_BT2020: color_space = SWS_CS_BT2020; break; + case BC_COLORS_BT2020_NCL: + case BC_COLORS_BT2020_CL: color_space = SWS_CS_BT2020; break; } const int *color_table = sws_getCoefficients(color_space); @@ -1664,6 +1878,7 @@ FFMPEG::FFMPEG(FileBase *file_base) flow = 1; decoding = encoding = 0; has_audio = has_video = 0; + interlace_from_codec = 0; opts = 0; opt_duration = -1; opt_video_filter = 0; @@ -1693,7 +1908,11 @@ FFMPEG::~FFMPEG() delete [] opt_hw_dev; } +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) +int FFMPEG::check_sample_rate(const AVCodec *codec, int sample_rate) +#else int FFMPEG::check_sample_rate(AVCodec *codec, int sample_rate) +#endif { const int *p = codec->supported_samplerates; if( !p ) return sample_rate; @@ -1704,20 +1923,22 @@ int FFMPEG::check_sample_rate(AVCodec *codec, int sample_rate) return 0; } +// check_frame_rate and std_frame_rate needed for 23.976 +// and 59.94 fps mpeg2 static inline AVRational std_frame_rate(int i) { static const int m1 = 1001*12, m2 = 1000*12; static const int freqs[] = { 40*m1, 48*m1, 50*m1, 60*m1, 80*m1,120*m1, 240*m1, - 24*m2, 30*m2, 60*m2, 12*m2, 15*m2, 48*m2, 0, + 24*m2, 30*m2, 60*m2, 12*m2, 15*m2, 48*m2, 90*m2, + 100*m2, 120*m2, 144*m2, 72*m2, 0, }; int freq = i<30*12 ? (i+1)*1001 : freqs[i-30*12]; return (AVRational) { freq, 1001*12 }; } -AVRational FFMPEG::check_frame_rate(AVCodec *codec, double frame_rate) +AVRational FFMPEG::check_frame_rate(const AVRational *p, double frame_rate) { - const AVRational *p = codec->supported_framerates; AVRational rate, best_rate = (AVRational) { 0, 0 }; double max_err = 1.; int i = 0; while( ((p ? (rate=*p++) : (rate=std_frame_rate(i++))), rate.num) != 0 ) { @@ -1941,7 +2162,11 @@ void FFMPEG::scan_audio_options(Asset *asset, EDL *edl) cin_fmt = (int)av_get_sample_fmt(cin_sample_fmt); if( cin_fmt < 0 ) { char audio_codec[BCSTRLEN]; audio_codec[0] = 0; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,18,100) + const AVCodec *av_codec = !FFMPEG::get_codec(audio_codec, "audio", asset->acodec) ? +#else AVCodec *av_codec = !FFMPEG::get_codec(audio_codec, "audio", asset->acodec) ? +#endif avcodec_find_encoder_by_name(audio_codec) : 0; if( av_codec && av_codec->sample_fmts ) cin_fmt = find_best_sample_fmt_of_list(av_codec->sample_fmts, AV_SAMPLE_FMT_FLT); @@ -1977,7 +2202,11 @@ void FFMPEG::scan_video_options(Asset *asset, EDL *edl) cin_fmt = (int)av_get_pix_fmt(cin_pix_fmt); if( cin_fmt < 0 ) { char video_codec[BCSTRLEN]; video_codec[0] = 0; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,18,100) + const AVCodec *av_codec = !get_codec(video_codec, "video", asset->vcodec) ? +#else AVCodec *av_codec = !get_codec(video_codec, "video", asset->vcodec) ? +#endif avcodec_find_encoder_by_name(video_codec) : 0; if( av_codec && av_codec->pix_fmts ) { if( 0 && edl ) { // frequently picks a bad answer @@ -2134,6 +2363,21 @@ int FFMPEG::scan_options(const char *options, AVDictionary *&opts, AVStream *st) return ret; } +void FFMPEG::put_cache_frame(VFrame *frame, int64_t position) +{ + file_base->file->put_cache_frame(frame, position, 0); +} + +int FFMPEG::get_use_cache() +{ + return file_base->file->get_use_cache(); +} + +void FFMPEG::purge_cache() +{ + file_base->file->purge_cache(); +} + FFCodecRemap::FFCodecRemap() { old_codec = 0; @@ -2156,10 +2400,15 @@ int FFCodecRemaps::add(const char *val) return 0; } - +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) +int FFCodecRemaps::update(AVCodecID &codec_id, const AVCodec *&decoder) +{ + const AVCodec *codec = avcodec_find_decoder(codec_id); +#else int FFCodecRemaps::update(AVCodecID &codec_id, AVCodec *&decoder) { AVCodec *codec = avcodec_find_decoder(codec_id); +#endif if( !codec ) return -1; const char *name = codec->name; FFCodecRemaps &map = *this; @@ -2285,12 +2534,44 @@ int FFMPEG::info(char *text, int len) AVPixelFormat pix_fmt = (AVPixelFormat)st->codecpar->format; const char *pfn = av_get_pix_fmt_name(pix_fmt); report(" pix %s\n", pfn ? pfn : unkn); + int interlace = st->codecpar->field_order; + report(" interlace (container level): %i\n", interlace ? interlace : -1); + int interlace_codec = interlace_from_codec; + report(" interlace (codec level): %i\n", interlace_codec ? interlace_codec : -1); enum AVColorSpace space = st->codecpar->color_space; const char *nm = av_color_space_name(space); report(" color space:%s", nm ? nm : unkn); enum AVColorRange range = st->codecpar->color_range; const char *rg = av_color_range_name(range); report("/ range:%s\n", rg ? rg : unkn); + + AVRational sar = av_guess_sample_aspect_ratio(fmt_ctx, st, NULL); + AVRational display_aspect_ratio; + if(sar.num) { + + av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, + st->codecpar->width * (int64_t)sar.num, + st->codecpar->height * (int64_t)sar.den, + 1024 * 1024); +/* report(" Guessed SAR: %d:%d, ", sar.num, sar.den ); + report("DAR: %d:%d \n", display_aspect_ratio.num, display_aspect_ratio.den); */ + } + if (st->sample_aspect_ratio.num) + { + av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, + st->codecpar->width * (int64_t)st->sample_aspect_ratio.num, + st->codecpar->height * (int64_t)st->sample_aspect_ratio.den, + 1024 * 1024); + report(" container Detected SAR: %d:%d , DAR %d:%d \n", st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, display_aspect_ratio.num, display_aspect_ratio.den); + } + if (st->codecpar->sample_aspect_ratio.num) + { + av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, + st->codecpar->width * (int64_t)st->codecpar->sample_aspect_ratio.num, + st->codecpar->height * (int64_t)st->codecpar->sample_aspect_ratio.den, + 1024 * 1024); + report(" codec Detected SAR: %d:%d , DAR %d:%d \n", st->codecpar->sample_aspect_ratio.num, st->codecpar->sample_aspect_ratio.den, display_aspect_ratio.num, display_aspect_ratio.den); + } double secs = to_secs(st->duration, st->time_base); int64_t length = secs * vid->frame_rate + 0.5; double ofs = to_secs((vid->nudge - st->start_time), st->time_base); @@ -2300,6 +2581,9 @@ int FFMPEG::info(char *text, int len) int hrs = secs/3600; secs -= hrs*3600; int mins = secs/60; secs -= mins*60; report(" %d:%02d:%05.2f\n", hrs, mins, secs); + double theta = vid->get_rotation_angle(); + if( fabs(theta) > 1 ) + report(" rotation angle: %0.1f\n", theta); } if( ffaudio.size() > 0 ) report("\n%d audio stream%s\n",ffaudio.size(), ffaudio.size()!=1 ? "s" : ""); @@ -2372,7 +2656,11 @@ int FFMPEG::init_decoder(const char *filename) char *sp = strrchr(bp, '.'); if( !sp ) sp = bp + strlen(bp); FILE *fp = 0; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,18,100) + const AVInputFormat *ifmt = 0; +#else AVInputFormat *ifmt = 0; +#endif if( sp ) { strcpy(sp, ".opts"); fp = fopen(file_opts, "r"); @@ -2465,18 +2753,22 @@ int FFMPEG::open_decoder() } switch( avpar->color_space ) { case AVCOL_SPC_BT470BG: + vid->color_space = BC_COLORS_BT601_PAL; + break; case AVCOL_SPC_SMPTE170M: - vid->color_space = BC_COLORS_BT601; + vid->color_space = BC_COLORS_BT601_NTSC; break; case AVCOL_SPC_BT709: vid->color_space = BC_COLORS_BT709; break; case AVCOL_SPC_BT2020_NCL: + vid->color_space = BC_COLORS_BT2020_NCL; + break; case AVCOL_SPC_BT2020_CL: - vid->color_space = BC_COLORS_BT2020; + vid->color_space = BC_COLORS_BT2020_CL; break; default: - vid->color_space = !file_base ? BC_COLORS_BT601 : + vid->color_space = !file_base ? BC_COLORS_BT601_NTSC : file_base->file->preferences->yuv_color_space; break; } @@ -2485,8 +2777,7 @@ int FFMPEG::open_decoder() vid->aspect_ratio = (double)st->sample_aspect_ratio.num / st->sample_aspect_ratio.den; vid->nudge = st->start_time; vid->reading = -1; - if( opt_video_filter ) - ret = vid->create_filter(opt_video_filter, avpar); + ret = vid->create_filter(opt_video_filter); break; } case AVMEDIA_TYPE_AUDIO: { if( avpar->channels < 1 ) continue; @@ -2505,8 +2796,7 @@ int FFMPEG::open_decoder() aud->init_swr(aud->channels, avpar->format, aud->sample_rate); aud->nudge = st->start_time; aud->reading = -1; - if( opt_audio_filter ) - ret = aud->create_filter(opt_audio_filter, avpar); + ret = aud->create_filter(opt_audio_filter); break; } default: break; } @@ -2591,7 +2881,11 @@ int FFMPEG::open_encoder(const char *type, const char *spec) AVCodecContext *ctx = 0; const AVCodecDescriptor *codec_desc = 0; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) + const AVCodec *codec = avcodec_find_encoder_by_name(codec_name); +#else AVCodec *codec = avcodec_find_encoder_by_name(codec_name); +#endif if( !codec ) { eprintf(_("cant find codec %s:%s\n"), codec_name, filename); ret = 1; @@ -2712,6 +3006,14 @@ int FFMPEG::open_encoder(const char *type, const char *spec) vid->width = asset->width; vid->height = asset->height; vid->frame_rate = asset->frame_rate; +#if 0 + char tc_str[20] = "00:00:00:00"; + double tc_offset; + if(asset->timecode > 0) + Units::totext(tc_str, asset->timecode, TIME_HMSF, 0, asset->frame_rate, 0); + //printf("tc: %s \n", tc_str); + av_dict_set(&st->metadata, "timecode", tc_str, 0); +#endif if( (vid->color_range = asset->ff_color_range) < 0 ) vid->color_range = file_base->file->preferences->yuv_color_range; switch( vid->color_range ) { @@ -2721,9 +3023,11 @@ int FFMPEG::open_encoder(const char *type, const char *spec) if( (vid->color_space = asset->ff_color_space) < 0 ) vid->color_space = file_base->file->preferences->yuv_color_space; switch( vid->color_space ) { - case BC_COLORS_BT601: ctx->colorspace = AVCOL_SPC_SMPTE170M; break; + case BC_COLORS_BT601_NTSC: ctx->colorspace = AVCOL_SPC_SMPTE170M; break; + case BC_COLORS_BT601_PAL: ctx->colorspace = AVCOL_SPC_BT470BG; break; case BC_COLORS_BT709: ctx->colorspace = AVCOL_SPC_BT709; break; - case BC_COLORS_BT2020: ctx->colorspace = AVCOL_SPC_BT2020_NCL; break; + case BC_COLORS_BT2020_NCL: ctx->colorspace = AVCOL_SPC_BT2020_NCL; break; + case BC_COLORS_BT2020_CL: ctx->colorspace = AVCOL_SPC_BT2020_CL; break; } AVPixelFormat pix_fmt = av_get_pix_fmt(asset->ff_pixel_format); if( opt_hw_dev != 0 ) { @@ -2746,7 +3050,12 @@ int FFMPEG::open_encoder(const char *type, const char *spec) int mask_h = (1<log2_chroma_h)-1; ctx->height = (vid->height+mask_h) & ~mask_h; ctx->sample_aspect_ratio = to_sample_aspect_ratio(asset); - AVRational frame_rate = check_frame_rate(codec, vid->frame_rate); + AVRational frame_rate; + if (ctx->codec->id == AV_CODEC_ID_MPEG1VIDEO || + ctx->codec->id == AV_CODEC_ID_MPEG2VIDEO) + frame_rate = check_frame_rate(codec->supported_framerates, vid->frame_rate); + else + frame_rate = av_d2q(vid->frame_rate, INT_MAX); if( !frame_rate.num || !frame_rate.den ) { eprintf(_("check_frame_rate failed %s\n"), filename); ret = 1; @@ -2756,12 +3065,38 @@ int FFMPEG::open_encoder(const char *type, const char *spec) frame_rate.num, frame_rate.den, INT_MAX); ctx->framerate = (AVRational) { frame_rate.num, frame_rate.den }; ctx->time_base = (AVRational) { frame_rate.den, frame_rate.num }; + if(!strcmp(format_name, "webm") || !strcmp(format_name, "matroska") || !strcmp(format_name, "mov") || + !strcmp(format_name, "qt") || !strcmp(format_name, "mp4") || !strcmp(format_name, "avi") || + !strcmp(format_name, "dv") || !strcmp(format_name, "yuv4mpegpipe")) + { + if (to_sample_aspect_ratio(asset).den > 0) + st->sample_aspect_ratio = to_sample_aspect_ratio(asset); + } st->avg_frame_rate = frame_rate; st->time_base = ctx->time_base; vid->writing = -1; vid->interlaced = asset->interlace_mode == ILACE_MODE_TOP_FIRST || asset->interlace_mode == ILACE_MODE_BOTTOM_FIRST ? 1 : 0; vid->top_field_first = asset->interlace_mode == ILACE_MODE_TOP_FIRST ? 1 : 0; + switch (asset->interlace_mode) { + case ILACE_MODE_TOP_FIRST: + if (ctx->codec->id == AV_CODEC_ID_MJPEG) + av_dict_set(&sopts, "field_order", "tt", 0); + else + av_dict_set(&sopts, "field_order", "tb", 0); + if (ctx->codec_id != AV_CODEC_ID_MJPEG) + av_dict_set(&sopts, "flags", "+ilme+ildct", 0); + break; + case ILACE_MODE_BOTTOM_FIRST: + if (ctx->codec->id == AV_CODEC_ID_MJPEG) + av_dict_set(&sopts, "field_order", "bb", 0); + else + av_dict_set(&sopts, "field_order", "bt", 0); + if (ctx->codec_id != AV_CODEC_ID_MJPEG) + av_dict_set(&sopts, "flags", "+ilme+ildct", 0); + break; + case ILACE_MODE_NOTINTERLACED: av_dict_set(&sopts, "field_order", "progressive", 0); break; + } break; } default: eprintf(_("not audio/video, %s:%s\n"), codec_name, filename); @@ -2818,7 +3153,11 @@ int FFMPEG::open_encoder(const char *type, const char *spec) } if( ret >= 0 ) { _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(58,134,100) ret = avcodec_copy_context(st->codec, ctx); +#else + ret = avcodec_parameters_to_context(ctx, st->codecpar); +#endif _Pragma("GCC diagnostic warning \"-Wdeprecated-declarations\"") if( ret < 0 ) fprintf(stderr, "Could not copy the stream context\n"); @@ -3054,6 +3393,33 @@ int FFMPEG::audio_seek(int stream, int64_t pos) return 0; } +int FFMPEG::video_probe(int64_t pos) +{ + int vidx = vstrm_index[0].st_idx; + FFVideoStream *vid = ffvideo[vidx]; + vid->probe(pos); + + int interlace1 = interlace_from_codec; + //printf("interlace from codec: %i\n", interlace1); + + switch (interlace1) + { + case AV_FIELD_TT: + case AV_FIELD_TB: + return ILACE_MODE_TOP_FIRST; + case AV_FIELD_BB: + case AV_FIELD_BT: + return ILACE_MODE_BOTTOM_FIRST; + case AV_FIELD_PROGRESSIVE: + return ILACE_MODE_NOTINTERLACED; + default: + return ILACE_MODE_UNDETECTED; + } + +} + + + int FFMPEG::video_seek(int stream, int64_t pos) { int vidx = vstrm_index[stream].st_idx; @@ -3303,25 +3669,29 @@ int FFMPEG::ff_total_vstreams() int FFMPEG::ff_video_width(int stream) { - return ffvideo[stream]->width; + FFVideoStream *vst = ffvideo[stream]; + return !vst->transpose ? vst->width : vst->height; } int FFMPEG::ff_video_height(int stream) { - return ffvideo[stream]->height; + FFVideoStream *vst = ffvideo[stream]; + return !vst->transpose ? vst->height : vst->width; } int FFMPEG::ff_set_video_width(int stream, int width) { - int w = ffvideo[stream]->width; - ffvideo[stream]->width = width; + FFVideoStream *vst = ffvideo[stream]; + int *vw = !vst->transpose ? &vst->width : &vst->height, w = *vw; + *vw = width; return w; } int FFMPEG::ff_set_video_height(int stream, int height) { - int h = ffvideo[stream]->height; - ffvideo[stream]->height = height; + FFVideoStream *vst = ffvideo[stream]; + int *vh = !vst->transpose ? &vst->height : &vst->width, h = *vh; + *vh = height; return h; } @@ -3337,7 +3707,20 @@ int FFMPEG::ff_coded_height(int stream) float FFMPEG::ff_aspect_ratio(int stream) { - return ffvideo[stream]->aspect_ratio; + //return ffvideo[stream]->aspect_ratio; + AVFormatContext *fmt_ctx = ffvideo[stream]->fmt_ctx; + AVStream *strm = ffvideo[stream]->st; + AVCodecParameters *par = ffvideo[stream]->st->codecpar; + AVRational dar; + AVRational sar = av_guess_sample_aspect_ratio(fmt_ctx, strm, NULL); + if (sar.num && ffvideo[stream]->get_rotation_angle() == 0) { + av_reduce(&dar.num, &dar.den, + par->width * sar.num, + par->height * sar.den, + 1024*1024); + return av_q2d(dar); + } + return ffvideo[stream]->aspect_ratio; } const char* FFMPEG::ff_video_codec(int stream) @@ -3378,9 +3761,33 @@ int FFMPEG::ff_video_mpeg_color_range(int stream) return ffvideo[stream]->st->codecpar->color_range == AVCOL_RANGE_MPEG ? 1 : 0; } +int FFMPEG::ff_interlace(int stream) +{ +// https://ffmpeg.org/doxygen/trunk/structAVCodecParserContext.html +/* reads from demuxer because codec frame not ready */ + int interlace0 = ffvideo[stream]->st->codecpar->field_order; + + switch (interlace0) + { + case AV_FIELD_TT: + case AV_FIELD_TB: + return ILACE_MODE_TOP_FIRST; + case AV_FIELD_BB: + case AV_FIELD_BT: + return ILACE_MODE_BOTTOM_FIRST; + case AV_FIELD_PROGRESSIVE: + return ILACE_MODE_NOTINTERLACED; + default: + return ILACE_MODE_UNDETECTED; + } + +} + + + int FFMPEG::ff_cpus() { - return file_base->file->cpus; + return !file_base ? 1 : file_base->file->cpus; } const char *FFMPEG::ff_hw_dev() @@ -3393,75 +3800,147 @@ Preferences *FFMPEG::ff_prefs() return !file_base ? 0 : file_base->file->preferences; } -int FFVideoStream::create_filter(const char *filter_spec, AVCodecParameters *avpar) +double FFVideoStream::get_rotation_angle() +{ +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) + size_t size = 0; +#else + int size = 0; +#endif + int *matrix = (int*)av_stream_get_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, &size); + int len = size/sizeof(*matrix); + if( !matrix || len < 5 ) return 0; + const double s = 1/65536.; + double theta = (!matrix[0] && !matrix[3]) || (!matrix[1] && !matrix[4]) ? 0 : + atan2( s*matrix[1] / hypot(s*matrix[1], s*matrix[4]), + s*matrix[0] / hypot(s*matrix[0], s*matrix[3])) * 180/M_PI; + return theta; +} + +int FFVideoStream::flip(double theta) +{ + int ret = 0; + transpose = 0; + Preferences *preferences = ffmpeg->ff_prefs(); + if( !preferences || !preferences->auto_rotate ) return ret; + double tolerance = 1; + if( fabs(theta-0) < tolerance ) return ret; + if( (theta=fmod(theta, 360)) < 0 ) theta += 360; + if( fabs(theta-90) < tolerance ) { + if( (ret = insert_filter("transpose", "clock")) < 0 ) + return ret; + transpose = 1; + } + else if( fabs(theta-180) < tolerance ) { + if( (ret=insert_filter("hflip", 0)) < 0 ) + return ret; + if( (ret=insert_filter("vflip", 0)) < 0 ) + return ret; + } + else if (fabs(theta-270) < tolerance ) { + if( (ret=insert_filter("transpose", "cclock")) < 0 ) + return ret; + transpose = 1; + } + else { + char angle[BCSTRLEN]; + sprintf(angle, "%f", theta*M_PI/180.); + if( (ret=insert_filter("rotate", angle)) < 0 ) + return ret; + } + return 1; +} + +int FFVideoStream::create_filter(const char *filter_spec) { + double theta = get_rotation_angle(); + if( !theta && !filter_spec ) + return 0; avfilter_register_all(); - const char *sp = filter_spec; - char filter_name[BCSTRLEN], *np = filter_name; - int i = sizeof(filter_name); - while( --i>=0 && *sp!=0 && !strchr(" \t:=,",*sp) ) *np++ = *sp++; - *np = 0; - const AVFilter *filter = !filter_name[0] ? 0 : avfilter_get_by_name(filter_name); - if( !filter || avfilter_pad_get_type(filter->inputs,0) != AVMEDIA_TYPE_VIDEO ) { - ff_err(AVERROR(EINVAL), "FFVideoStream::create_filter: %s\n", filter_spec); - return -1; + if( filter_spec ) { + const char *sp = filter_spec; + char filter_name[BCSTRLEN], *np = filter_name; + int i = sizeof(filter_name); + while( --i>=0 && *sp!=0 && !strchr(" \t:=,",*sp) ) *np++ = *sp++; + *np = 0; + const AVFilter *filter = !filter_name[0] ? 0 : avfilter_get_by_name(filter_name); + if( !filter || avfilter_pad_get_type(filter->inputs,0) != AVMEDIA_TYPE_VIDEO ) { + ff_err(AVERROR(EINVAL), "FFVideoStream::create_filter: %s\n", filter_spec); + return -1; + } } - filter_graph = avfilter_graph_alloc(); - const AVFilter *buffersrc = avfilter_get_by_name("buffer"); - const AVFilter *buffersink = avfilter_get_by_name("buffersink"); + AVCodecParameters *avpar = st->codecpar; + int sa_num = avpar->sample_aspect_ratio.num; + if( !sa_num ) sa_num = 1; + int sa_den = avpar->sample_aspect_ratio.den; + if( !sa_den ) sa_num = 1; int ret = 0; char args[BCTEXTLEN]; AVPixelFormat pix_fmt = (AVPixelFormat)avpar->format; snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", avpar->width, avpar->height, (int)pix_fmt, - st->time_base.num, st->time_base.den, - avpar->sample_aspect_ratio.num, avpar->sample_aspect_ratio.den); - if( ret >= 0 ) - ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", - args, NULL, filter_graph); - if( ret >= 0 ) - ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", - NULL, NULL, filter_graph); + st->time_base.num, st->time_base.den, sa_num, sa_den); + if( ret >= 0 ) { + filt_ctx = 0; + ret = insert_filter("buffer", args, "in"); + buffersrc_ctx = filt_ctx; + } if( ret >= 0 ) + ret = flip(theta); + AVFilterContext *fsrc = filt_ctx; + if( ret >= 0 ) { + filt_ctx = 0; + ret = insert_filter("buffersink", 0, "out"); + buffersink_ctx = filt_ctx; + } + if( ret >= 0 ) { ret = av_opt_set_bin(buffersink_ctx, "pix_fmts", (uint8_t*)&pix_fmt, sizeof(pix_fmt), AV_OPT_SEARCH_CHILDREN); - if( ret < 0 ) - ff_err(ret, "FFVideoStream::create_filter"); + } + if( ret >= 0 ) + ret = config_filters(filter_spec, fsrc); else - ret = FFStream::create_filter(filter_spec); + ff_err(ret, "FFVideoStream::create_filter"); return ret >= 0 ? 0 : -1; } -int FFAudioStream::create_filter(const char *filter_spec, AVCodecParameters *avpar) +int FFAudioStream::create_filter(const char *filter_spec) { + if( !filter_spec ) + return 0; avfilter_register_all(); - const char *sp = filter_spec; - char filter_name[BCSTRLEN], *np = filter_name; - int i = sizeof(filter_name); - while( --i>=0 && *sp!=0 && !strchr(" \t:=,",*sp) ) *np++ = *sp++; - *np = 0; - const AVFilter *filter = !filter_name[0] ? 0 : avfilter_get_by_name(filter_name); - if( !filter || avfilter_pad_get_type(filter->inputs,0) != AVMEDIA_TYPE_AUDIO ) { - ff_err(AVERROR(EINVAL), "FFAudioStream::create_filter: %s\n", filter_spec); - return -1; + if( filter_spec ) { + const char *sp = filter_spec; + char filter_name[BCSTRLEN], *np = filter_name; + int i = sizeof(filter_name); + while( --i>=0 && *sp!=0 && !strchr(" \t:=,",*sp) ) *np++ = *sp++; + *np = 0; + const AVFilter *filter = !filter_name[0] ? 0 : avfilter_get_by_name(filter_name); + if( !filter || avfilter_pad_get_type(filter->inputs,0) != AVMEDIA_TYPE_AUDIO ) { + ff_err(AVERROR(EINVAL), "FFAudioStream::create_filter: %s\n", filter_spec); + return -1; + } } - filter_graph = avfilter_graph_alloc(); - const AVFilter *buffersrc = avfilter_get_by_name("abuffer"); - const AVFilter *buffersink = avfilter_get_by_name("abuffersink"); int ret = 0; char args[BCTEXTLEN]; + AVCodecParameters *avpar = st->codecpar; AVSampleFormat sample_fmt = (AVSampleFormat)avpar->format; snprintf(args, sizeof(args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%jx", st->time_base.num, st->time_base.den, avpar->sample_rate, av_get_sample_fmt_name(sample_fmt), avpar->channel_layout); - if( ret >= 0 ) - ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", - args, NULL, filter_graph); - if( ret >= 0 ) - ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", - NULL, NULL, filter_graph); + if( ret >= 0 ) { + filt_ctx = 0; + ret = insert_filter("abuffer", args, "in"); + buffersrc_ctx = filt_ctx; + } + AVFilterContext *fsrc = filt_ctx; + if( ret >= 0 ) { + filt_ctx = 0; + ret = insert_filter("abuffersink", 0, "out"); + buffersink_ctx = filt_ctx; + } if( ret >= 0 ) ret = av_opt_set_bin(buffersink_ctx, "sample_fmts", (uint8_t*)&sample_fmt, sizeof(sample_fmt), @@ -3474,45 +3953,126 @@ int FFAudioStream::create_filter(const char *filter_spec, AVCodecParameters *avp ret = av_opt_set_bin(buffersink_ctx, "sample_rates", (uint8_t*)&sample_rate, sizeof(sample_rate), AV_OPT_SEARCH_CHILDREN); - if( ret < 0 ) - ff_err(ret, "FFAudioStream::create_filter"); + if( ret >= 0 ) + ret = config_filters(filter_spec, fsrc); else - ret = FFStream::create_filter(filter_spec); + ff_err(ret, "FFAudioStream::create_filter"); return ret >= 0 ? 0 : -1; } -int FFStream::create_filter(const char *filter_spec) +int FFStream::insert_filter(const char *name, const char *arg, const char *inst_name) { - /* Endpoints for the filter graph. */ - AVFilterInOut *outputs = avfilter_inout_alloc(); - outputs->name = av_strdup("in"); - outputs->filter_ctx = buffersrc_ctx; - outputs->pad_idx = 0; - outputs->next = 0; - - AVFilterInOut *inputs = avfilter_inout_alloc(); - inputs->name = av_strdup("out"); - inputs->filter_ctx = buffersink_ctx; - inputs->pad_idx = 0; - inputs->next = 0; - - int ret = !outputs->name || !inputs->name ? -1 : 0; + const AVFilter *filter = avfilter_get_by_name(name); + if( !filter ) return -1; + char filt_inst[BCSTRLEN]; + if( !inst_name ) { + snprintf(filt_inst, sizeof(filt_inst), "%s_%d", name, ++filt_id); + inst_name = filt_inst; + } + if( !filter_graph ) + filter_graph = avfilter_graph_alloc(); + AVFilterContext *fctx = 0; + int ret = avfilter_graph_create_filter(&fctx, + filter, inst_name, arg, NULL, filter_graph); + if( ret >= 0 && filt_ctx ) + ret = avfilter_link(filt_ctx, 0, fctx, 0); if( ret >= 0 ) - ret = avfilter_graph_parse_ptr(filter_graph, filter_spec, - &inputs, &outputs, NULL); + filt_ctx = fctx; + else + avfilter_free(fctx); + return ret; +} + +int FFStream::config_filters(const char *filter_spec, AVFilterContext *fsrc) +{ + int ret = 0; + AVFilterContext *fsink = buffersink_ctx; + if( filter_spec ) { + /* Endpoints for the filter graph. */ + AVFilterInOut *outputs = avfilter_inout_alloc(); + AVFilterInOut *inputs = avfilter_inout_alloc(); + if( !inputs || !outputs ) ret = -1; + if( ret >= 0 ) { + outputs->filter_ctx = fsrc; + outputs->pad_idx = 0; + outputs->next = 0; + if( !(outputs->name = av_strdup(fsrc->name)) ) ret = -1; + } + if( ret >= 0 ) { + inputs->filter_ctx = fsink; + inputs->pad_idx = 0; + inputs->next = 0; + if( !(inputs->name = av_strdup(fsink->name)) ) ret = -1; + } + if( ret >= 0 ) { + int len = strlen(fsrc->name)+2 + strlen(filter_spec) + 1; + char spec[len]; sprintf(spec, "[%s]%s", fsrc->name, filter_spec); + ret = avfilter_graph_parse_ptr(filter_graph, spec, + &inputs, &outputs, NULL); + } + avfilter_inout_free(&inputs); + avfilter_inout_free(&outputs); + } + else + ret = avfilter_link(fsrc, 0, fsink, 0); if( ret >= 0 ) ret = avfilter_graph_config(filter_graph, NULL); - if( ret < 0 ) { ff_err(ret, "FFStream::create_filter"); avfilter_graph_free(&filter_graph); filter_graph = 0; } - avfilter_inout_free(&inputs); - avfilter_inout_free(&outputs); return ret; } + +AVCodecContext *FFMPEG::activate_decoder(AVStream *st) +{ + AVDictionary *copts = 0; + av_dict_copy(&copts, opts, 0); + AVCodecID codec_id = st->codecpar->codec_id; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) + const AVCodec *decoder = 0; +#else + AVCodec *decoder = 0; +#endif + switch( st->codecpar->codec_type ) { + case AVMEDIA_TYPE_VIDEO: + if( opt_video_decoder ) + decoder = avcodec_find_decoder_by_name(opt_video_decoder); + else + video_codec_remaps.update(codec_id, decoder); + break; + case AVMEDIA_TYPE_AUDIO: + if( opt_audio_decoder ) + decoder = avcodec_find_decoder_by_name(opt_audio_decoder); + else + audio_codec_remaps.update(codec_id, decoder); + break; + default: + return 0; + } + if( !decoder && !(decoder = avcodec_find_decoder(codec_id)) ) { + eprintf(_("cant find decoder codec %d\n"), (int)codec_id); + return 0; + } + AVCodecContext *avctx = avcodec_alloc_context3(decoder); + if( !avctx ) { + eprintf(_("cant allocate codec context\n")); + return 0; + } + avcodec_parameters_to_context(avctx, st->codecpar); + if( !av_dict_get(copts, "threads", NULL, 0) ) + avctx->thread_count = ff_cpus(); + int ret = avcodec_open2(avctx, decoder, &copts); + av_dict_free(&copts); + if( ret < 0 ) { + avcodec_free_context(&avctx); + avctx = 0; + } + return avctx; +} + int FFMPEG::scan(IndexState *index_state, int64_t *scan_position, int *canceled) { AVPacket pkt; @@ -3529,25 +4089,9 @@ int FFMPEG::scan(IndexState *index_state, int64_t *scan_position, int *canceled) index_state->add_audio_markers(ffaudio.size()); for( int i=0; i<(int)fmt_ctx->nb_streams; ++i ) { - int ret = 0; - AVDictionary *copts = 0; - av_dict_copy(&copts, opts, 0); AVStream *st = fmt_ctx->streams[i]; - AVCodecID codec_id = st->codecpar->codec_id; - AVCodec *decoder = avcodec_find_decoder(codec_id); - AVCodecContext *avctx = avcodec_alloc_context3(decoder); - if( !avctx ) { - eprintf(_("cant allocate codec context\n")); - ret = AVERROR(ENOMEM); - } - if( ret >= 0 ) { - avcodec_parameters_to_context(avctx, st->codecpar); - if( !av_dict_get(copts, "threads", NULL, 0) ) - avctx->thread_count = ff_cpus(); - ret = avcodec_open2(avctx, decoder, &copts); - } - av_dict_free(&copts); - if( ret >= 0 ) { + AVCodecContext *avctx = activate_decoder(st); + if( avctx ) { AVCodecParameters *avpar = st->codecpar; switch( avpar->codec_type ) { case AVMEDIA_TYPE_VIDEO: { @@ -3615,8 +4159,8 @@ int FFMPEG::scan(IndexState *index_state, int64_t *scan_position, int *canceled) if( vidx < 0 ) break; FFVideoStream *vid = ffvideo[vidx]; if( !vid->avctx ) break; - int64_t tstmp = pkt.dts; - if( tstmp == AV_NOPTS_VALUE ) tstmp = pkt.pts; + int64_t tstmp = pkt.pts; + if( tstmp == AV_NOPTS_VALUE ) tstmp = pkt.dts; if( tstmp != AV_NOPTS_VALUE && (pkt.flags & AV_PKT_FLAG_KEY) && pkt.pos > 0 ) { if( vid->nudge != AV_NOPTS_VALUE ) tstmp -= vid->nudge; double secs = to_secs(tstmp, st->time_base); @@ -3683,10 +4227,20 @@ void FFStream::load_markers(IndexMarks &marks, double rate) int in = 0; int64_t sz = marks.size(); int max_entries = fmt_ctx->max_index_size / sizeof(AVIndexEntry) - 1; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) + int nb_ent = avformat_index_get_entries_count(st); +#endif +#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(58,134,100) int nb_ent = st->nb_index_entries; +#endif // some formats already have an index if( nb_ent > 0 ) { +#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(58,134,100) AVIndexEntry *ep = &st->index_entries[nb_ent-1]; +#endif +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59,16,100) + const AVIndexEntry *ep = avformat_index_get_entry(st, nb_ent-1); +#endif int64_t tstmp = ep->timestamp; if( nudge != AV_NOPTS_VALUE ) tstmp -= nudge; double secs = ffmpeg->to_secs(tstmp, st->time_base); @@ -3706,3 +4260,249 @@ void FFStream::load_markers(IndexMarks &marks, double rate) } } + +/* + * 1) if the format context has a timecode + * return fmt_ctx->timecode - 0 + * 2) if the layer/channel has a timecode + * return st->timecode - (start_time-nudge) + * 3) find the 1st program with stream, find 1st program video stream, + * if video stream has a timecode, return st->timecode - (start_time-nudge) + * 4) find timecode in any stream, return st->timecode + * 5) read 100 packets, save ofs=pkt.pts*st->time_base - st->nudge: + * decode frame for video stream of 1st program + * if frame->timecode has a timecode, return frame->timecode - ofs + * if side_data has gop timecode, return gop->timecode - ofs + * if side_data has smpte timecode, return smpte->timecode - ofs + * 6) if the filename/url scans *date_time.ext, return date_time + * 7) if stat works on the filename/url, return mtime + * 8) return -1 failure +*/ +double FFMPEG::get_initial_timecode(int data_type, int channel, double frame_rate) +{ + AVRational rate = check_frame_rate(0, frame_rate); + if( !rate.num ) return -1; +// format context timecode + AVDictionaryEntry *tc = av_dict_get(fmt_ctx->metadata, "timecode", 0, 0); + if( tc ) return ff_get_timecode(tc->value, rate, 0); +// stream timecode + if( open_decoder() ) return -1; + AVStream *st = 0; + int64_t nudge = 0; + int codec_type = -1, fidx = -1; + switch( data_type ) { + case TRACK_AUDIO: { + codec_type = AVMEDIA_TYPE_AUDIO; + int aidx = astrm_index[channel].st_idx; + FFAudioStream *aud = ffaudio[aidx]; + fidx = aud->fidx; + nudge = aud->nudge; + st = aud->st; + AVDictionaryEntry *tref = av_dict_get(fmt_ctx->metadata, "time_reference", 0, 0); + if( tref && aud && aud->sample_rate ) + return strtod(tref->value, 0) / aud->sample_rate; + break; } + case TRACK_VIDEO: { + codec_type = AVMEDIA_TYPE_VIDEO; + int vidx = vstrm_index[channel].st_idx; + FFVideoStream *vid = ffvideo[vidx]; + fidx = vid->fidx; + nudge = vid->nudge; + st = vid->st; + break; } + } + if( codec_type < 0 ) return -1; + if( st ) + tc = av_dict_get(st->metadata, "timecode", 0, 0); + if( !tc ) { + st = 0; +// find first program which references this stream + int pidx = -1; + for( int i=0, m=fmt_ctx->nb_programs; pidx<0 && iprograms[i]; + for( int j=0, n=pgrm->nb_stream_indexes; jstream_index[j]; + if( st_idx == fidx ) { pidx = i; break; } + } + } + fidx = -1; + if( pidx >= 0 ) { + AVProgram *pgrm = fmt_ctx->programs[pidx]; + for( int j=0, n=pgrm->nb_stream_indexes; jstream_index[j]; + AVStream *tst = fmt_ctx->streams[st_idx]; + if( !tst ) continue; + if( tst->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ) { + st = tst; fidx = st_idx; + break; + } + } + } + else { + for( int i=0, n=fmt_ctx->nb_streams; istreams[i]; + if( !tst ) continue; + if( tst->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ) { + st = tst; fidx = i; + break; + } + } + } + if( st ) + tc = av_dict_get(st->metadata, "timecode", 0, 0); + } + + if( !tc ) { + // any timecode, includes -data- streams + for( int i=0, n=fmt_ctx->nb_streams; istreams[i]; + if( !tst ) continue; + if( (tc = av_dict_get(tst->metadata, "timecode", 0, 0)) ) { + st = tst; fidx = i; + break; + } + } + } + + if( st && st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ) { + if( st->r_frame_rate.num && st->r_frame_rate.den ) + rate = st->r_frame_rate; + nudge = st->start_time; + for( int i=0; ist == st ) { + nudge = ffvideo[i]->nudge; + break; + } + } + } + + if( tc ) { // return timecode + double secs = st->start_time == AV_NOPTS_VALUE ? 0 : + to_secs(st->start_time - nudge, st->time_base); + return ff_get_timecode(tc->value, rate, secs); + } + + if( !st || fidx < 0 ) return -1; + + decode_activate(); + AVCodecContext *av_ctx = activate_decoder(st); + if( !av_ctx ) { + fprintf(stderr,"activate_decoder failed\n"); + return -1; + } + avCodecContext avctx(av_ctx); // auto deletes + if( avctx->codec_type == AVMEDIA_TYPE_VIDEO && + avctx->framerate.num && avctx->framerate.den ) + rate = avctx->framerate; + + avPacket pkt; // auto deletes + avFrame frame; // auto deletes + if( !frame ) { + fprintf(stderr,"av_frame_alloc failed\n"); + return -1; + } + int errs = 0; + int64_t max_packets = 100; + char tcbuf[AV_TIMECODE_STR_SIZE]; + + for( int64_t count=0; countdata = 0; pkt->size = 0; + + int ret = av_read_frame(fmt_ctx, pkt); + if( ret < 0 ) { + if( ret == AVERROR_EOF ) break; + if( ++errs > 100 ) { + fprintf(stderr,"over 100 read_frame errs\n"); + break; + } + continue; + } + if( !pkt->data ) continue; + int i = pkt->stream_index; + if( i != fidx ) continue; + int64_t tstmp = pkt->pts; + if( tstmp == AV_NOPTS_VALUE ) tstmp = pkt->dts; + double secs = to_secs(tstmp - nudge, st->time_base); + ret = avcodec_send_packet(avctx, pkt); + if( ret < 0 ) return -1; + + while( (ret = avcodec_receive_frame(avctx, frame)) >= 0 ) { + if( (tc = av_dict_get(frame->metadata, "timecode", 0, 0)) ) + return ff_get_timecode(tc->value, rate, secs); + int k = frame->nb_side_data; + AVFrameSideData *side_data = 0; + while( --k >= 0 ) { + side_data = frame->side_data[k]; + switch( side_data->type ) { + case AV_FRAME_DATA_GOP_TIMECODE: { + int64_t data = *(int64_t *)side_data->data; + int sz = sizeof(data); + if( side_data->size >= sz ) { + av_timecode_make_mpeg_tc_string(tcbuf, data); + return ff_get_timecode(tcbuf, rate, secs); + } + break; } + case AV_FRAME_DATA_S12M_TIMECODE: { + uint32_t *data = (uint32_t *)side_data->data; + int n = data[0], sz = (n+1)*sizeof(*data); + if( side_data->size >= sz ) { + av_timecode_make_smpte_tc_string(tcbuf, data[n], 0); + return ff_get_timecode(tcbuf, rate, secs); + } + break; } + default: + break; + } + } + } + } + char *path = fmt_ctx->url; + char *bp = strrchr(path, '/'); + if( !bp ) bp = path; else ++bp; + char *cp = strrchr(bp, '.'); + if( cp && (cp-=(8+1+6)) >= bp ) { + char sep[BCSTRLEN]; + int year,mon,day, hour,min,sec, frm=0; + if( sscanf(cp,"%4d%2d%2d%[_-]%2d%2d%2d", + &year,&mon,&day, sep, &hour,&min,&sec) == 7 ) { + int ch = sep[0]; + // year>=1970,mon=1..12,day=1..31, hour=0..23,min=0..59,sec=0..60 + if( (ch=='_' || ch=='-' ) && + year >= 1970 && mon>=1 && mon<=12 && day>=1 && day<=31 && + hour>=0 && hour<24 && min>=0 && min<60 && sec>=0 && sec<=60 ) { + sprintf(tcbuf,"%d:%02d:%02d:%02d", hour,min,sec, frm); + return ff_get_timecode(tcbuf, rate, 0); + } + } + } + struct stat tst; + if( stat(path, &tst) >= 0 ) { + time_t t = (time_t)tst.st_mtim.tv_sec; + struct tm tm; + localtime_r(&t, &tm); + int64_t us = tst.st_mtim.tv_nsec / 1000; + int frm = us/1000000. * frame_rate; + sprintf(tcbuf,"%d:%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec, frm); + return ff_get_timecode(tcbuf, rate, 0); + } + return -1; +} + +double FFMPEG::ff_get_timecode(char *str, AVRational rate, double pos) +{ + AVTimecode tc; + if( av_timecode_init_from_string(&tc, rate, str, fmt_ctx) ) + return -1; + double secs = (double)tc.start / tc.fps - pos; + if( secs < 0 ) secs = 0; + return secs; +} + +double FFMPEG::get_timecode(const char *path, int data_type, int channel, double rate) +{ + FFMPEG ffmpeg(0); + if( ffmpeg.init_decoder(path) ) return -1; + return ffmpeg.get_initial_timecode(data_type, channel, rate); +} +