X-Git-Url: https://git.cinelerra-gg.org/git/?p=goodguy%2Fcinelerra.git;a=blobdiff_plain;f=cinelerra-5.1%2Fcinelerra%2Fffmpeg.C;h=df39763c3e30033db7c0d640d96d2d22a0c745f2;hp=bf6a6fe61786c8f104434d525240ae2ce15c9a8b;hb=97ed925c86313b57c13a2db0fb9aa48822fe76ba;hpb=750c685e0f667cfe57bdb5eaf3cd2a061f44b01d diff --git a/cinelerra-5.1/cinelerra/ffmpeg.C b/cinelerra-5.1/cinelerra/ffmpeg.C index bf6a6fe6..df39763c 100644 --- a/cinelerra-5.1/cinelerra/ffmpeg.C +++ b/cinelerra-5.1/cinelerra/ffmpeg.C @@ -263,12 +263,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 +288,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 +299,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); @@ -428,14 +433,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,17 +449,17 @@ 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; - av_seek_frame(fmt_ctx, idx, INT64_MIN, flags); + av_seek_frame(fmt_ctx, idx, 0, flags); need_packet = 1; flushed = 0; seeked = 1; st_eof(0); ret = 0; continue; } - probe_frame = frame; + probe_frame = hw_frame; if( ret >= 0 ) reading = 1; else @@ -491,7 +496,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 +519,7 @@ int FFStream::decode(AVFrame *frame) flushed = st_eof(); } } + frm_lock->unlock(); if( retries < 0 ) { fprintf(stderr, "FFStream::decode: Retry limit\n"); @@ -709,6 +715,7 @@ 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; @@ -726,6 +733,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 +766,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 +782,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 +1055,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 +1063,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() @@ -1086,6 +1100,7 @@ int FFVideoStream::decode_hw_format(AVCodec *decoder, AVHWDeviceType type) if( !config ) { fprintf(stderr, "Decoder %s does not support device type %s.\n", decoder->name, av_hwdevice_get_type_name(type)); + ret = -1; break; } if( (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0 && @@ -1198,6 +1213,39 @@ 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; + return ret; +} + int FFVideoStream::load(VFrame *vframe, int64_t pos) { int ret = video_seek(pos); @@ -1206,11 +1254,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 ) { @@ -1252,6 +1328,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; @@ -1278,6 +1426,7 @@ int FFVideoStream::encode(VFrame *vframe) int FFVideoStream::drain() { + return 0; } @@ -1663,6 +1812,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; @@ -1714,9 +1864,8 @@ static inline AVRational std_frame_rate(int i) 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 ) { @@ -2133,6 +2282,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; @@ -2284,6 +2448,10 @@ 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); @@ -2299,6 +2467,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" : ""); @@ -2484,8 +2655,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; @@ -2504,8 +2674,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; } @@ -2745,7 +2914,7 @@ 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 = check_frame_rate(codec->supported_framerates, vid->frame_rate); if( !frame_rate.num || !frame_rate.den ) { eprintf(_("check_frame_rate failed %s\n"), filename); ret = 1; @@ -2761,6 +2930,25 @@ int FFMPEG::open_encoder(const char *type, const char *spec) 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); @@ -3053,6 +3241,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; @@ -3302,25 +3517,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; } @@ -3336,7 +3555,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) { + 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) @@ -3377,9 +3609,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() @@ -3392,75 +3648,143 @@ 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() +{ + int size = 0; + 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), @@ -3473,45 +3797,122 @@ 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; + AVCodec *decoder = 0; + 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; @@ -3528,25 +3929,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: { @@ -3614,8 +3999,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); @@ -3705,3 +4090,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); +} +