X-Git-Url: http://git.cinelerra-gg.org/git/?a=blobdiff_plain;f=cinelerra-5.1%2Fcinelerra%2Fffmpeg.C;h=ba0e0a34ecdd9260c579297bb81302c134f49056;hb=3cbe62ca61e2b35d3e228978f6cdedd11218077f;hp=493be6abeace4e55e9eac4177964649a63c6fc2e;hpb=3643286e2dbb3002604a62e3e6fd834716b4ae63;p=goodguy%2Fcinelerra.git diff --git a/cinelerra-5.1/cinelerra/ffmpeg.C b/cinelerra-5.1/cinelerra/ffmpeg.C index 493be6ab..ba0e0a34 100644 --- a/cinelerra-5.1/cinelerra/ffmpeg.C +++ b/cinelerra-5.1/cinelerra/ffmpeg.C @@ -285,6 +285,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 +296,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); @@ -491,7 +493,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 +516,7 @@ int FFStream::decode(AVFrame *frame) flushed = st_eof(); } } + frm_lock->unlock(); if( retries < 0 ) { fprintf(stderr, "FFStream::decode: Retry limit\n"); @@ -726,6 +729,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 +762,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 +778,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 +1051,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; @@ -1652,6 +1660,44 @@ IndexMarks *FFVideoStream::get_markers() return !index_state ? 0 : index_state->video_markers[idx]; } +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; +} + +void FFVideoStream::flip() +{ + transpose = 0; + if( !ffmpeg->file_base ) return; + double theta = get_rotation_angle(), tolerance = 1; + if( fabs(theta-0) < tolerance ) return; + if( fabs(theta-90) < tolerance ) { + create_filter("transpose=clock", st->codecpar); + transpose = 1; + } + else if( fabs(theta-180) < tolerance ) { + create_filter("hflip", st->codecpar); + create_filter("vflip", st->codecpar); + } + else if (fabs(theta-270) < tolerance ) { + create_filter("transpose=cclock", st->codecpar); + transpose = 1; + } + else { + char rotate[BCSTRLEN]; + sprintf(rotate, "rotate=%f", theta*M_PI/180.); + create_filter(rotate, st->codecpar); + } +} + FFMPEG::FFMPEG(FileBase *file_base) { @@ -1715,9 +1761,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 ) { @@ -2300,6 +2345,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" : ""); @@ -2487,6 +2535,8 @@ int FFMPEG::open_decoder() vid->reading = -1; if( opt_video_filter ) ret = vid->create_filter(opt_video_filter, avpar); + if( file_base && file_base->file->preferences->auto_rotate ) + vid->flip(); break; } case AVMEDIA_TYPE_AUDIO: { if( avpar->channels < 1 ) continue; @@ -2746,7 +2796,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; @@ -3303,25 +3353,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; } @@ -3380,7 +3434,7 @@ int FFMPEG::ff_video_mpeg_color_range(int stream) int FFMPEG::ff_cpus() { - return file_base->file->cpus; + return !file_base ? 1 : file_base->file->cpus; } const char *FFMPEG::ff_hw_dev() @@ -3409,14 +3463,17 @@ int FFVideoStream::create_filter(const char *filter_spec, AVCodecParameters *avp filter_graph = avfilter_graph_alloc(); const AVFilter *buffersrc = avfilter_get_by_name("buffer"); const AVFilter *buffersink = avfilter_get_by_name("buffersink"); + 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); + st->time_base.num, st->time_base.den, sa_num, sa_den); if( ret >= 0 ) ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph); @@ -3513,6 +3570,50 @@ int FFStream::create_filter(const char *filter_spec) 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; @@ -3529,25 +3630,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 +3700,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); @@ -3706,3 +3791,246 @@ 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; + 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); +} +