diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f02bffcd96..e82aebb1fe 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -46,12 +46,6 @@ jobs: cache: true cache-dependency-path: go.sum - - name: Get the latest install_ffmpeg.sh from go-livepeer - run: | - rm install_ffmpeg.sh || true - curl -LO https://raw.githubusercontent.com/livepeer/go-livepeer/master/install_ffmpeg.sh - chmod +x ./install_ffmpeg.sh - - name: Cache ffmpeg id: cache-ffmpeg uses: actions/cache@v4 diff --git a/ffmpeg/api_test.go b/ffmpeg/api_test.go index 57e33a4e9e..607e5ade89 100755 --- a/ffmpeg/api_test.go +++ b/ffmpeg/api_test.go @@ -258,132 +258,132 @@ func countEncodedFrames(t *testing.T, accel Acceleration) { cmd = ` cat << EOF > expected_pts.out ==> out_120fps_0.ts.pts <== -pkt_pts=129000 -pkt_pts=129750 -pkt_pts=130500 -pkt_pts=306000 -pkt_pts=306750 -pkt_pts=307500 +pts=129000 +pts=129750 +pts=130500 +pts=306000 +pts=306750 +pts=307500 ==> out_120fps_1.ts.pts <== -pkt_pts=309000 -pkt_pts=309750 -pkt_pts=310500 -pkt_pts=486000 -pkt_pts=486750 -pkt_pts=487500 +pts=309000 +pts=309750 +pts=310500 +pts=486000 +pts=486750 +pts=487500 ==> out_120fps_2.ts.pts <== -pkt_pts=489000 -pkt_pts=489750 -pkt_pts=490500 -pkt_pts=666000 -pkt_pts=666750 -pkt_pts=667500 +pts=489000 +pts=489750 +pts=490500 +pts=666000 +pts=666750 +pts=667500 ==> out_120fps_3.ts.pts <== -pkt_pts=669000 -pkt_pts=669750 -pkt_pts=670500 -pkt_pts=846000 -pkt_pts=846750 -pkt_pts=847500 +pts=669000 +pts=669750 +pts=670500 +pts=846000 +pts=846750 +pts=847500 ==> out_30fps_0.ts.pts <== -pkt_pts=129000 -pkt_pts=132000 -pkt_pts=135000 -pkt_pts=300000 -pkt_pts=303000 -pkt_pts=306000 +pts=129000 +pts=132000 +pts=135000 +pts=300000 +pts=303000 +pts=306000 ==> out_30fps_1.ts.pts <== -pkt_pts=309000 -pkt_pts=312000 -pkt_pts=315000 -pkt_pts=483000 -pkt_pts=486000 -pkt_pts=489000 +pts=309000 +pts=312000 +pts=315000 +pts=483000 +pts=486000 +pts=489000 ==> out_30fps_2.ts.pts <== -pkt_pts=489000 -pkt_pts=492000 -pkt_pts=495000 -pkt_pts=660000 -pkt_pts=663000 -pkt_pts=666000 +pts=489000 +pts=492000 +pts=495000 +pts=660000 +pts=663000 +pts=666000 ==> out_30fps_3.ts.pts <== -pkt_pts=669000 -pkt_pts=672000 -pkt_pts=675000 -pkt_pts=843000 -pkt_pts=846000 -pkt_pts=849000 +pts=669000 +pts=672000 +pts=675000 +pts=843000 +pts=846000 +pts=849000 ==> out_60fps_0.ts.pts <== -pkt_pts=129000 -pkt_pts=130500 -pkt_pts=132000 -pkt_pts=304500 -pkt_pts=306000 -pkt_pts=307500 +pts=129000 +pts=130500 +pts=132000 +pts=304500 +pts=306000 +pts=307500 ==> out_60fps_1.ts.pts <== -pkt_pts=309000 -pkt_pts=310500 -pkt_pts=312000 -pkt_pts=484500 -pkt_pts=486000 -pkt_pts=487500 +pts=309000 +pts=310500 +pts=312000 +pts=484500 +pts=486000 +pts=487500 ==> out_60fps_2.ts.pts <== -pkt_pts=489000 -pkt_pts=490500 -pkt_pts=492000 -pkt_pts=664500 -pkt_pts=666000 -pkt_pts=667500 +pts=489000 +pts=490500 +pts=492000 +pts=664500 +pts=666000 +pts=667500 ==> out_60fps_3.ts.pts <== -pkt_pts=669000 -pkt_pts=670500 -pkt_pts=672000 -pkt_pts=844500 -pkt_pts=846000 -pkt_pts=847500 +pts=669000 +pts=670500 +pts=672000 +pts=844500 +pts=846000 +pts=847500 ==> out_passthru_0.ts.pts <== -pkt_pts=128970 -pkt_pts=130500 -pkt_pts=131940 -pkt_pts=304470 -pkt_pts=305910 -pkt_pts=307440 +pts=128970 +pts=130500 +pts=131940 +pts=304470 +pts=305910 +pts=307440 ==> out_passthru_1.ts.pts <== -pkt_pts=308970 -pkt_pts=310500 -pkt_pts=311940 -pkt_pts=484470 -pkt_pts=485910 -pkt_pts=487440 +pts=308970 +pts=310500 +pts=311940 +pts=484470 +pts=485910 +pts=487440 ==> out_passthru_2.ts.pts <== -pkt_pts=488970 -pkt_pts=490410 -pkt_pts=491940 -pkt_pts=664470 -pkt_pts=665910 -pkt_pts=667440 +pts=488970 +pts=490410 +pts=491940 +pts=664470 +pts=665910 +pts=667440 ==> out_passthru_3.ts.pts <== -pkt_pts=668970 -pkt_pts=670500 -pkt_pts=671940 -pkt_pts=844470 -pkt_pts=845910 -pkt_pts=847440 +pts=668970 +pts=670500 +pts=671940 +pts=844470 +pts=845910 +pts=847440 EOF ` run(cmd) @@ -395,8 +395,8 @@ EOF for f in $FILES do - ffprobe -loglevel warning -select_streams v -show_frames $f | grep pkt_pts= | head -3 > $f.pts - ffprobe -loglevel warning -select_streams v -show_frames $f | grep pkt_pts= | tail -3 >> $f.pts + ffprobe -loglevel warning -select_streams v -show_frames $f | grep pts= | head -3 > $f.pts + ffprobe -loglevel warning -select_streams v -show_frames $f | grep pts= | tail -3 >> $f.pts done tail -n +1 out_*.ts.pts > transcoded_pts.out @@ -518,7 +518,6 @@ func shortSegments(t *testing.T, accel Acceleration, fc int) { for i := 0; i < 4; i++ { fname := fmt.Sprintf("%s/short%d.ts", dir, i) oname := fmt.Sprintf("%s/out%d.ts", dir, i) - t.Log("fname ", fname) in := &TranscodeOptionsIn{Fname: fname, Accel: accel} out := []TranscodeOptions{{Oname: oname, Profile: P144p30fps16x9, Accel: accel}} res, err := tc.Transcode(in, out) @@ -617,15 +616,15 @@ func shortSegments(t *testing.T, accel Acceleration, fc int) { cmd = ` frame_count=%d # convert segment to 3fps and trim it to #fc frames - ffmpeg -loglevel warning -i test.ts -vf fps=3/1 -c:v libx264 -c:a copy -frames:v $frame_count short3fps.ts + ffmpeg -loglevel warning -i test.ts -vf fps=3/1 -c:v libx264 -c:a copy -frames:v $frame_count short3fps.mp4 # sanity check - ffprobe -loglevel warning -show_streams short3fps.ts | grep r_frame_rate=3/1 - ffprobe -loglevel warning -count_frames -show_streams -select_streams v short3fps.ts | grep nb_read_frames=$frame_count + ffprobe -loglevel warning -show_streams short3fps.mp4 | grep r_frame_rate=3/1 + ffprobe -loglevel warning -count_frames -show_streams -select_streams v short3fps.mp4 | grep nb_read_frames=$frame_count ` run(fmt.Sprintf(cmd, fc)) - fname := fmt.Sprintf("%s/short3fps.ts", dir) + fname := fmt.Sprintf("%s/short3fps.mp4", dir) in := &TranscodeOptionsIn{Fname: fname, Accel: accel} out := []TranscodeOptions{{Oname: dir + "/out1fps.ts", Profile: P144p30fps16x9, Accel: accel}} out[0].Profile.Framerate = 1 // Force 1fps @@ -775,7 +774,8 @@ func consecutiveMP4s(t *testing.T, accel Acceleration) { defer os.RemoveAll(dir) cmd := ` cp "$1"/../transcoder/test.ts . - ffmpeg -i test.ts -c copy -f segment test%d.mp4 + # use segment_time_delta on mp4 to correctly capture keyframes + ffmpeg -i test.ts -c copy -f segment -segment_time_delta 0.1 test%d.mp4 ffmpeg -i test.ts -c copy -f segment test%d.ts # manually convert ts to mp4 just to sanity check @@ -1044,9 +1044,9 @@ func setGops(t *testing.T, accel Acceleration) { prepare out3.ts # extremely low frame rate - ffmpeg -loglevel warning -i test.ts -c:v libx264 -r 1 lowfps.ts - ffprobe -loglevel warning lowfps.ts -select_streams v -show_packets | grep flags= | wc -l | grep 9 - ffprobe -loglevel warning lowfps.ts -select_streams v -show_packets | grep flags=K | wc -l | grep 1 + ffmpeg -loglevel warning -i test.ts -c:a copy -c:v libx264 -r 1 lowfps.ts + ffprobe -loglevel warning -select_streams v -show_packets lowfps.ts | grep flags= | wc -l | grep 10 + ffprobe -loglevel warning -select_streams v -show_packets lowfps.ts | grep flags=K | wc -l | grep 1 ` run(cmd) @@ -1113,19 +1113,19 @@ func setGops(t *testing.T, accel Acceleration) { check passthrough3.ts # low framerate checks. sanity check number of packets vs keyframes - ffprobe -loglevel warning lpms_lowfps.ts -select_streams v -show_packets | grep flags= | wc -l | grep 9 + ffprobe -loglevel warning lpms_lowfps.ts -select_streams v -show_packets | grep flags= | wc -l | grep 10 ffprobe -loglevel warning lpms_lowfps.ts -select_streams v -show_packets | grep flags=K | wc -l | grep 5 # intra checks with passthrough fps. # sanity check number of packets vs keyframes - ffprobe -loglevel warning lpms_intra.ts -select_streams v -show_packets| grep flags= | wc -l | grep 9 - ffprobe -loglevel warning lpms_intra.ts -select_streams v -show_packets|grep flags=K | wc -l | grep 9 + ffprobe -loglevel warning lpms_intra.ts -select_streams v -show_packets| grep flags= | wc -l | grep 10 + ffprobe -loglevel warning lpms_intra.ts -select_streams v -show_packets|grep flags=K | wc -l | grep 10 # intra checks with fixed fps. # sanity check number of packets vs keyframes - # TODO look into why lpms only generates 81 frames instead of 90 - ffprobe -loglevel warning lpms_intra_10fps.ts -select_streams v -show_packets | grep flags= | wc -l | grep 81 - ffprobe -loglevel warning lpms_intra_10fps.ts -select_streams v -show_packets | grep flags=K | wc -l | grep 81 + # TODO look into why lpms generates 91 frames instead of 100 + ffprobe -loglevel warning lpms_intra_10fps.ts -select_streams v -show_packets | grep flags= | wc -l | grep 91 + ffprobe -loglevel warning lpms_intra_10fps.ts -select_streams v -show_packets | grep flags=K | wc -l | grep 91 ` run(cmd) diff --git a/ffmpeg/decoder.c b/ffmpeg/decoder.c index 2c344ee17a..67123c5f92 100755 --- a/ffmpeg/decoder.c +++ b/ffmpeg/decoder.c @@ -209,7 +209,7 @@ static enum AVPixelFormat get_hw_pixfmt(AVCodecContext *vc, const enum AVPixelFo int open_audio_decoder(input_params *params, struct input_ctx *ctx) { int ret = 0; - AVCodec *codec = NULL; + const AVCodec *codec = NULL; AVFormatContext *ic = ctx->ic; // open audio decoder @@ -270,7 +270,7 @@ char* get_hw_decoder(int ff_codec_id, int hw_type) int open_video_decoder(input_params *params, struct input_ctx *ctx) { int ret = 0; - AVCodec *codec = NULL; + const AVCodec *codec = NULL; AVDictionary **opts = NULL; AVFormatContext *ic = ctx->ic; // open video decoder @@ -285,7 +285,7 @@ int open_video_decoder(input_params *params, struct input_ctx *ctx) ret = lpms_ERR_INPUT_CODEC; LPMS_ERR(open_decoder_err, "Input codec does not support hardware acceleration"); } - AVCodec *c = avcodec_find_decoder_by_name(decoder_name); + const AVCodec *c = avcodec_find_decoder_by_name(decoder_name); if (c) codec = c; else LPMS_WARN("Nvidia decoder not found; defaulting to software"); if (AV_PIX_FMT_YUV420P != ic->streams[ctx->vi]->codecpar->format && @@ -296,7 +296,7 @@ int open_video_decoder(input_params *params, struct input_ctx *ctx) } } else if (params->video.name && strlen(params->video.name) != 0) { // Try to find user specified decoder by name - AVCodec *c = avcodec_find_decoder_by_name(params->video.name); + const AVCodec *c = avcodec_find_decoder_by_name(params->video.name); if (c) codec = c; if (params->video.opts) opts = ¶ms->video.opts; } diff --git a/ffmpeg/encoder.c b/ffmpeg/encoder.c index a767cd8340..1e1fd3f7f0 100755 --- a/ffmpeg/encoder.c +++ b/ffmpeg/encoder.c @@ -109,10 +109,10 @@ static int add_audio_stream(struct input_ctx *ictx, struct output_ctx *octx) } static int open_audio_output(struct input_ctx *ictx, struct output_ctx *octx, - AVOutputFormat *fmt) + const AVOutputFormat *fmt) { int ret = 0; - AVCodec *codec = NULL; + const AVCodec *codec = NULL; AVCodecContext *ac = NULL; // add audio encoder if a decoder exists and this output requires one @@ -130,8 +130,8 @@ static int open_audio_output(struct input_ctx *ictx, struct output_ctx *octx, if (!ac) LPMS_ERR(audio_output_err, "Unable to alloc audio encoder"); octx->ac = ac; ac->sample_fmt = av_buffersink_get_format(octx->af.sink_ctx); - ac->channel_layout = av_buffersink_get_channel_layout(octx->af.sink_ctx); - ac->channels = av_buffersink_get_channels(octx->af.sink_ctx); + ret = av_buffersink_get_ch_layout(octx->af.sink_ctx, &ac->ch_layout); + if (ret < 0) LPMS_ERR(audio_output_err, "Unable to initialize channel layout"); ac->sample_rate = av_buffersink_get_sample_rate(octx->af.sink_ctx); ac->time_base = av_buffersink_get_time_base(octx->af.sink_ctx); if (fmt->flags & AVFMT_GLOBALHEADER) ac->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; @@ -210,10 +210,10 @@ int open_output(struct output_ctx *octx, struct input_ctx *ictx) { int ret = 0, inp_has_stream; - AVOutputFormat *fmt = NULL; + const AVOutputFormat *fmt = NULL; + const AVCodec *codec = NULL; AVFormatContext *oc = NULL; AVCodecContext *vc = NULL; - AVCodec *codec = NULL; // open muxer fmt = av_guess_format(octx->muxer->name, octx->fname, NULL); @@ -241,8 +241,9 @@ int open_output(struct output_ctx *octx, struct input_ctx *ictx) else if (ictx->vc->framerate.num && ictx->vc->framerate.den) vc->framerate = ictx->vc->framerate; else vc->framerate = ictx->ic->streams[ictx->vi]->r_frame_rate; if (octx->fps.den) vc->time_base = av_buffersink_get_time_base(octx->vf.sink_ctx); - else if (ictx->vc->time_base.num && ictx->vc->time_base.den) vc->time_base = ictx->vc->time_base; + else if (ictx->vc->framerate.num && ictx->vc->framerate.den) vc->time_base = av_inv_q(ictx->vc->framerate); else vc->time_base = ictx->ic->streams[ictx->vi]->time_base; + vc->flags |= AV_CODEC_FLAG_COPY_OPAQUE; if (octx->bitrate) vc->rc_min_rate = vc->bit_rate = vc->rc_max_rate = vc->rc_buffer_size = octx->bitrate; if (av_buffersink_get_hw_frames_ctx(octx->vf.sink_ctx)) { vc->hw_frames_ctx = @@ -300,7 +301,7 @@ int reopen_output(struct output_ctx *octx, struct input_ctx *ictx) { int ret = 0; // re-open muxer for HW encoding - AVOutputFormat *fmt = av_guess_format(octx->muxer->name, octx->fname, NULL); + const AVOutputFormat *fmt = av_guess_format(octx->muxer->name, octx->fname, NULL); if (!fmt) LPMS_ERR(reopen_out_err, "Unable to guess format for reopen"); ret = avformat_alloc_output_context2(&octx->oc, fmt, NULL, octx->fname); if (ret < 0) LPMS_ERR(reopen_out_err, "Unable to alloc reopened out context"); @@ -368,7 +369,14 @@ static int encode(AVCodecContext* encoder, AVFrame *frame, struct output_ctx* oc ret = avcodec_receive_packet(encoder, pkt); if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) goto encode_cleanup; if (ret < 0) LPMS_ERR(encode_cleanup, "Error receiving packet from encoder"); - ret = mux(pkt, encoder->time_base, octx, ost); + AVRational time_base = encoder->time_base; + if (AVMEDIA_TYPE_VIDEO == ost->codecpar->codec_type && !octx->fps.den && octx->vf.active) { + // try to preserve source timestamps for fps passthrough. + time_base = octx->vf.time_base; + pkt->pts = (int64_t)pkt->opaque; // already in filter timebase + pkt->dts = av_rescale_q(pkt->dts, encoder->time_base, time_base); + } + ret = mux(pkt, time_base, octx, ost); if (ret < 0) goto encode_cleanup; } @@ -527,6 +535,16 @@ int process_out(struct input_ctx *ictx, struct output_ctx *octx, AVCodecContext ret = calc_signature(frame, octx); if(ret < 0) LPMS_WARN("Could not calculate signature value for frame"); } + + if (frame) { + // rescale pts to match encoder timebase if necessary (eg, fps passthrough) + AVRational filter_tb = av_buffersink_get_time_base(filter->sink_ctx); + if (av_cmp_q(filter_tb, encoder->time_base)) { + frame->pts = av_rescale_q(frame->pts, filter_tb, encoder->time_base); + // TODO does frame->duration needs to be rescaled too? + } + } + ret = encode(encoder, frame, octx, ost); skip: av_frame_unref(frame); diff --git a/ffmpeg/extras.c b/ffmpeg/extras.c index 05910be29a..37b910c35e 100644 --- a/ffmpeg/extras.c +++ b/ffmpeg/extras.c @@ -42,11 +42,11 @@ int lpms_rtmp2hls(char *listen, char *outf, char *ts_tmpl, char* seg_time, char int ret = 0; AVFormatContext *ic = NULL; AVFormatContext *oc = NULL; - AVOutputFormat *ofmt = NULL; + const AVOutputFormat *ofmt = NULL; AVStream *ist = NULL; AVStream *ost = NULL; AVDictionary *md = NULL; - AVCodec *codec = NULL; + const AVCodec *codec = NULL; AVPacket *pkt = NULL; int64_t prev_ts[2] = {AV_NOPTS_VALUE, AV_NOPTS_VALUE}; int stream_map[2] = {-1, -1}; @@ -148,7 +148,7 @@ int lpms_get_codec_info(char *fname, pcodec_info out) { #define MIN(x, y) (((x) < (y)) ? (x) : (y)) AVFormatContext *ic = NULL; - AVCodec *ac, *vc; + const AVCodec *ac, *vc; int ret = GET_CODEC_OK, vstream = 0, astream = 0; ret = avformat_open_input(&ic, fname, NULL, NULL); diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go index 944095c66a..5c59128dfe 100644 --- a/ffmpeg/ffmpeg_test.go +++ b/ffmpeg/ffmpeg_test.go @@ -266,7 +266,7 @@ func TestTranscoder_SampleRate(t *testing.T) { # 1024 = samples per frame, 48000 = samples per second # select last frame pts, subtract from first frame pts, check diff - ffprobe -loglevel warning -show_frames -select_streams a "$2" | grep pkt_pts= | head -"$1" | awk 'BEGIN{FS="="} ; NR==1 { fst = $2 } ; END{ diff=(($2-fst)/90000); exit diff <= 0.979 || diff >= 1.021 }' + ffprobe -loglevel warning -show_frames -select_streams a "$2" | grep pts= | head -"$1" | awk 'BEGIN{FS="="} ; NR==1 { fst = $2 } ; END{ diff=(($2-fst)/90000); exit diff <= 0.979 || diff >= 1.021 }' EOF chmod +x check_ts @@ -317,7 +317,7 @@ func TestTranscoder_Timestamp(t *testing.T) { grep r_frame_rate=60 inp.out # reduce 60fps original to 30fps indicated but 15fps real - ffmpeg -loglevel warning -i inp.ts -t 1 -c:v libx264 -an -vf select='not(mod(n\,4))' -r 30 test.ts + ffmpeg -loglevel warning -i inp.ts -an -vf 'fps=30,select=not(mod(n\,2))' -c:v libx264 -t 1 -fps_mode vfr test.ts ffprobe -loglevel warning -select_streams v -show_streams -count_frames test.ts > test.out # sanity check some properties. hard code numbers for now. @@ -1469,10 +1469,8 @@ func TestTranscoder_PassthroughFPS(t *testing.T) { ffprobe -v warning -show_streams test-short.ts | grep r_frame_rate=60/1 ffprobe -v warning -show_streams test-123fps.mp4 | grep r_frame_rate=123/1 # Extract frame properties for later comparison - ffprobe -v warning -select_streams v -show_frames test-123fps.mp4 | grep duration= > test-123fps.duration - ffprobe -v warning -select_streams v -show_frames test-short.ts | grep duration= > test-short.duration - ffprobe -v warning -select_streams v -show_frames test-123fps.mp4 | grep pkt_pts= > test-123fps.pts - ffprobe -v warning -select_streams v -show_frames test-short.ts | grep pkt_pts= > test-short.pts + ffprobe -v warning -select_streams v -show_frames -show_entries frame=pts,pkt_dts,duration -of csv test-123fps.mp4 > test-123fps.data + ffprobe -v warning -select_streams v -show_frames -show_entries frame=pts,pkt_dts,duration -of csv test-short.ts > test-short.data ` run(cmd) out := []TranscodeOptions{{Profile: P144p30fps16x9}} @@ -1510,14 +1508,10 @@ func TestTranscoder_PassthroughFPS(t *testing.T) { ffprobe -v warning -show_streams out-123fps.mp4 | grep r_frame_rate=123/1 # Check some per-frame properties - ffprobe -v warning -select_streams v -show_frames out-123fps.mp4 | grep duration= > out-123fps.duration - ffprobe -v warning -select_streams v -show_frames out-short.ts | grep duration= > out-short.duration - diff -u test-123fps.duration out-123fps.duration - # diff -u test-short.duration out-short.duration # Why does this fail??? - ffprobe -v warning -select_streams v -show_frames out-123fps.mp4 | grep pkt_pts= > out-123fps.pts - ffprobe -v warning -select_streams v -show_frames out-short.ts | grep pkt_pts= > out-short.pts - diff -u test-123fps.pts out-123fps.pts - diff -u test-short.pts out-short.pts + ffprobe -v warning -select_streams v -show_frames -show_entries frame=pts,pkt_dts,duration -of csv out-123fps.mp4 > out-123fps.data + ffprobe -v warning -select_streams v -show_frames -show_entries frame=pts,pkt_dts,duration -of csv out-short.ts > out-short.data + diff -u test-123fps.data out-123fps.data + diff -u test-short.data test-short.data ` run(cmd) } @@ -1570,7 +1564,7 @@ func TestTranscoder_FormatOptions(t *testing.T) { } cmd = ` # Check playlist - ffprobe -loglevel warning -show_format actually_hls.flv | grep format_name=hls + head -1 actually_hls.flv | grep "#EXTM3U" # Check that (copied) mpegts stream matches source ls -lha test_segment_*.ts | wc -l | grep 4 # sanity check four segments cat test_segment_*.ts > segment.ts @@ -1605,7 +1599,7 @@ func TestTranscoder_FormatOptions(t *testing.T) { cat <<- EOF > expected_mp4.out [FORMAT] format_name=mov,mp4,m4a,3gp,3g2,mj2 - duration=8.033000 + duration=8.032667 [/FORMAT] EOF @@ -1913,9 +1907,9 @@ func TestTranscoder_VFR(t *testing.T) { if(eq(N, 27), 33455340,\ if(eq(N, 28), 33458040,\ if(eq(N, 29), 33459750, 0\ - ))))))))))))))))))))))))))))))',scale=320:240" -c:v libx264 -bf 0 -frames:v 30 -copyts -enc_time_base 1:90000 -vsync passthrough -muxdelay 0 in.ts + ))))))))))))))))))))))))))))))',scale=320:240" -c:v libx264 -bf 0 -frames:v 30 -copyts -enc_time_base 1:90000 -fps_mode vfr -muxdelay 0 in.ts - ffprobe -hide_banner -i in.ts -select_streams v:0 -show_entries packet=pts,duration -of csv=p=0 | sed '/^$/d' > input-pts.out + ffprobe -hide_banner -i in.ts -select_streams v:0 -show_entries packet=pts,duration -of csv=p=0 | sed '/^$/d' | sed 's/,*$//g' > input-pts.out # Double check that we've correctly generated the expected pts for input cat << PTS_EOF > expected-input-pts.out diff --git a/ffmpeg/filter.c b/ffmpeg/filter.c index d2f24bbceb..9bacc1ec46 100644 --- a/ffmpeg/filter.c +++ b/ffmpeg/filter.c @@ -5,6 +5,7 @@ #include #include +#include #include @@ -73,14 +74,16 @@ int init_video_filters(struct input_ctx *ictx, struct output_ctx *octx) ret = AVERROR(ENOMEM); LPMS_ERR(vf_init_cleanup, "Unable to allocate filters"); } + vf->time_base = time_base; if (ictx->vc->hw_device_ctx) in_pix_fmt = hw2pixfmt(ictx->vc); /* buffer video source: the decoded frames from the decoder will be inserted here. */ snprintf(args, sizeof args, - "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d:colorspace=%s:range=%s", ictx->vc->width, ictx->vc->height, in_pix_fmt, time_base.num, time_base.den, - ictx->vc->sample_aspect_ratio.num, ictx->vc->sample_aspect_ratio.den); + ictx->vc->sample_aspect_ratio.num, ictx->vc->sample_aspect_ratio.den, + av_color_space_name(ictx->vc->colorspace), av_color_range_name(ictx->vc->color_range)); ret = avfilter_graph_create_filter(&vf->src_ctx, buffersrc, "in", args, NULL, vf->graph); @@ -130,6 +133,7 @@ int init_audio_filters(struct input_ctx *ictx, struct output_ctx *octx) int ret = 0; char args[512]; char filters_descr[256]; + char channel_layout[256]; const AVFilter *buffersrc = avfilter_get_by_name("abuffer"); const AVFilter *buffersink = avfilter_get_by_name("abuffersink"); AVFilterInOut *outputs = NULL; @@ -151,11 +155,13 @@ int init_audio_filters(struct input_ctx *ictx, struct output_ctx *octx) } /* buffer audio source: the decoded frames from the decoder will be inserted here. */ + ret = av_channel_layout_describe(&ictx->ac->ch_layout, channel_layout, sizeof(channel_layout)); + if (ret < 0) LPMS_ERR(af_init_cleanup, "Unable to describe audio channel layout"); snprintf(args, sizeof args, - "sample_rate=%d:sample_fmt=%d:channel_layout=0x%"PRIx64":channels=%d:" + "sample_rate=%d:sample_fmt=%d:channel_layout=%s:channels=%d:" "time_base=%d/%d", - ictx->ac->sample_rate, ictx->ac->sample_fmt, ictx->ac->channel_layout, - ictx->ac->channels, time_base.num, time_base.den); + ictx->ac->sample_rate, ictx->ac->sample_fmt, channel_layout, + ictx->ac->ch_layout.nb_channels, time_base.num, time_base.den); // TODO set sample format and rate based on encoder support, // rather than hardcoding @@ -298,6 +304,7 @@ int filtergraph_write(AVFrame *inf, struct input_ctx *ictx, struct output_ctx *o filter->custom_pts += ts_step; filter->prev_frame_pts = inf->pts; } else { + // FPS Passthrough or Audio case filter->custom_pts = inf->pts; } } else if (!filter->flushed) { // Flush Frame @@ -310,7 +317,7 @@ int filtergraph_write(AVFrame *inf, struct input_ctx *ictx, struct output_ctx *o ts_step = av_rescale_q_rnd(1, av_inv_q(octx->fps), vst->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); } else { // FPS Passthrough or Audio case - use packet duration instead of custom duration - ts_step = inf->pkt_duration; + ts_step = inf->duration; } filter->custom_pts += ts_step; } @@ -344,6 +351,7 @@ int filtergraph_read(struct input_ctx *ictx, struct output_ctx *octx, struct fil if (filter->flushing) filter->flushed = 1; ret = lpms_ERR_FILTER_FLUSHED; } else if (frame && is_video && octx->fps.den) { + // TODO why limit to fps filter? what about non-fps filtergraphs, eg scale? // We set custom PTS as an input of the filtergraph so we need to // re-calculate our output PTS before passing it on to the encoder if (filter->pts_diff == INT64_MIN) { diff --git a/ffmpeg/filter.h b/ffmpeg/filter.h index 0204c2a67a..59d9e8c945 100755 --- a/ffmpeg/filter.h +++ b/ffmpeg/filter.h @@ -13,6 +13,9 @@ struct filter_ctx { uint8_t *hwframes; // GPU frame pool data + // Input timebase for this filter + AVRational time_base; + // The fps filter expects monotonically increasing PTS, which might not hold // for our input segments (they may be out of order, or have dropped frames). // So we set a custom PTS before sending the frame to the filtergraph that is diff --git a/ffmpeg/nvidia_test.go b/ffmpeg/nvidia_test.go index 5f9c84bdd9..7ff777b292 100755 --- a/ffmpeg/nvidia_test.go +++ b/ffmpeg/nvidia_test.go @@ -355,6 +355,8 @@ func TestNvidia_Devices(t *testing.T) { Accel: Software, }, }) + // if this fails with a generic error, it probably means + // ffmpeg error code from cuda init needs to be patched if err == nil || err.Error() != "No such device" { t.Error(fmt.Errorf(fmt.Sprintf("\nError being: '%v'\n", err))) } @@ -371,6 +373,8 @@ func TestNvidia_Devices(t *testing.T) { Device: "9999", }, }) + // if this fails with a generic error, it probably means + // ffmpeg error code from cuda init needs to be patched if err == nil || err.Error() != "No such device" { t.Error(fmt.Errorf(fmt.Sprintf("\nError being: '%v'\n", err))) } @@ -423,8 +427,8 @@ func TestNvidia_DrainFilters(t *testing.T) { ffprobe -loglevel warning -show_streams -select_streams v -count_frames out.ts > probe.out # These used to be same, but aren't since we've diverged the flushing and PTS handling from ffmpeg - grep nb_read_frames=100 probe.out - grep duration=1.0000 probe.out + grep nb_read_frames=101 probe.out + grep duration=1.0100 probe.out grep nb_read_frames=102 ffmpeg.out grep duration=1.0200 ffmpeg.out ` @@ -555,9 +559,9 @@ func TestNvidia_API_MixedOutput(t *testing.T) { # sanity check ffmpeg frame count against ours ffprobe -count_frames -show_streams -select_streams v ffmpeg_nv_$1.ts | grep nb_read_frames=246 - ffprobe -count_frames -show_streams -select_streams v nv_$1.ts | grep nb_read_frames=246 - ffprobe -count_frames -show_streams -select_streams v sw_$1.ts | grep nb_read_frames=246 - ffprobe -count_frames -show_streams -select_streams v nv_audio_encode_$1.ts | grep nb_read_frames=246 + ffprobe -count_frames -show_streams -select_streams v nv_$1.ts | grep nb_read_frames=245 + ffprobe -count_frames -show_streams -select_streams v sw_$1.ts | grep nb_read_frames=245 + ffprobe -count_frames -show_streams -select_streams v nv_audio_encode_$1.ts | grep nb_read_frames=245 # check image quality ffmpeg -loglevel warning -i nv_$1.ts -i ffmpeg_nv_$1.ts \ @@ -646,9 +650,9 @@ func TestNvidia_API_AlternatingTimestamps(t *testing.T) { # sanity check ffmpeg frame count against ours ffprobe -count_frames -show_streams -select_streams v ffmpeg_nv_$1.ts | grep nb_read_frames=246 - ffprobe -count_frames -show_streams -select_streams v nv_$1.ts | grep nb_read_frames=246 - ffprobe -count_frames -show_streams -select_streams v sw_$1.ts | grep nb_read_frames=246 - ffprobe -count_frames -show_streams -select_streams v nv_audio_encode_$1.ts | grep nb_read_frames=246 + ffprobe -count_frames -show_streams -select_streams v nv_$1.ts | grep nb_read_frames=245 + ffprobe -count_frames -show_streams -select_streams v sw_$1.ts | grep nb_read_frames=245 + ffprobe -count_frames -show_streams -select_streams v nv_audio_encode_$1.ts | grep nb_read_frames=245 # check image quality ffmpeg -loglevel warning -i nv_$1.ts -i ffmpeg_nv_$1.ts \ @@ -665,7 +669,6 @@ func TestNvidia_API_AlternatingTimestamps(t *testing.T) { } - # re-enable for seg 0 and 1 when alternating timestamps can be handled check 0 check 1 check 2 diff --git a/ffmpeg/transcoder.c b/ffmpeg/transcoder.c index 0247f5657e..345fb4aebf 100755 --- a/ffmpeg/transcoder.c +++ b/ffmpeg/transcoder.c @@ -286,15 +286,15 @@ int handle_audio_frame(struct transcode_thread *h, AVStream *ist, output_results // frame duration update int64_t dur = 0; - if (dframe->pkt_duration) { - dur = dframe->pkt_duration; + if (dframe->duration) { + dur = dframe->duration; } else if (ist->r_frame_rate.den) { dur = av_rescale_q(1, av_inv_q(ist->r_frame_rate), ist->time_base); } else { // TODO use better heuristics for this; look at how ffmpeg does it LPMS_WARN("Could not determine next pts; filter might drop"); } - dframe->pkt_duration = dur; + dframe->duration = dur; // keep as last frame av_frame_unref(ictx->last_frame_a); @@ -326,15 +326,15 @@ int handle_video_frame(struct transcode_thread *h, AVStream *ist, output_results // frame duration update int64_t dur = 0; - if (dframe->pkt_duration) { - dur = dframe->pkt_duration; + if (dframe->duration) { + dur = dframe->duration; } else if (ist->r_frame_rate.den) { dur = av_rescale_q(1, av_inv_q(ist->r_frame_rate), ist->time_base); } else { // TODO use better heuristics for this; look at how ffmpeg does it LPMS_WARN("Could not determine next pts; filter might drop"); } - dframe->pkt_duration = dur; + dframe->duration = dur; // keep as last frame av_frame_unref(ictx->last_frame_v); @@ -742,14 +742,14 @@ int transcode(struct transcode_thread *h, // if there is frame, update duration and put this frame in place as last_frame if (has_frame) { int64_t dur = 0; - if (dframe->pkt_duration) dur = dframe->pkt_duration; + if (dframe->duration) dur = dframe->duration; else if (ist->r_frame_rate.den) { dur = av_rescale_q(1, av_inv_q(ist->r_frame_rate), ist->time_base); } else { // TODO use better heuristics for this; look at how ffmpeg does it LPMS_WARN("Could not determine next pts; filter might drop"); } - dframe->pkt_duration = dur; + dframe->duration = dur; av_frame_unref(last_frame); av_frame_ref(last_frame, dframe); } diff --git a/ffmpeg/transmuxer_test.go b/ffmpeg/transmuxer_test.go index 340ebc8a9b..ce39ac9e25 100644 --- a/ffmpeg/transmuxer_test.go +++ b/ffmpeg/transmuxer_test.go @@ -163,7 +163,7 @@ func TestTransmuxer_Discontinuity(t *testing.T) { tc.StopTranscoder() cmd = ` ffprobe -loglevel warning -select_streams v -count_frames -show_streams out.mp4 | grep nb_read_frames=960 - ffprobe -loglevel warning -select_streams v -count_frames -show_streams -show_frames out.mp4 | grep pkt_pts=1441410 + ffprobe -loglevel warning -select_streams v -count_frames -show_streams -show_frames out.mp4 | grep pts=1441410 ` run(cmd) } diff --git a/install_ffmpeg.sh b/install_ffmpeg.sh index d187373cfd..36900ba13c 100755 --- a/install_ffmpeg.sh +++ b/install_ffmpeg.sh @@ -1,3 +1,231 @@ #!/usr/bin/env bash -echo 'WARNING: downloading and executing go-livepeer/install_ffmpeg.sh, use it directly in case of issues' -wget -O - https://raw.githubusercontent.com/livepeer/go-livepeer/master/install_ffmpeg.sh | bash -s $1 \ No newline at end of file + +set -exuo pipefail + +ROOT="${1:-$HOME}" +NPROC=${NPROC:-$(nproc)} +EXTRA_CFLAGS="" +EXTRA_LDFLAGS="" +EXTRA_X264_FLAGS="" +EXTRA_FFMPEG_FLAGS="" +BUILD_TAGS="${BUILD_TAGS:-}" + +# Build platform flags +BUILDOS=$(uname -s | tr '[:upper:]' '[:lower:]') +BUILDARCH=$(uname -m | tr '[:upper:]' '[:lower:]') +if [[ $BUILDARCH == "aarch64" ]]; then + BUILDARCH=arm64 +fi +if [[ $BUILDARCH == "x86_64" ]]; then + BUILDARCH=amd64 +fi + +# Override these for cross-compilation +export GOOS="${GOOS:-$BUILDOS}" +export GOARCH="${GOARCH:-$BUILDARCH}" + +echo "BUILDOS: $BUILDOS" +echo "BUILDARCH: $BUILDARCH" +echo "GOOS: $GOOS" +echo "GOARCH: $GOARCH" + +function check_sysroot() { + if ! stat $SYSROOT > /dev/null; then + echo "cross-compilation sysroot not found at $SYSROOT, try setting SYSROOT to the correct path" + exit 1 + fi +} + +if [[ "$BUILDARCH" == "amd64" && "$BUILDOS" == "linux" && "$GOARCH" == "arm64" && "$GOOS" == "linux" ]]; then + echo "cross-compiling linux-amd64 --> linux-arm64" + export CC="clang-14" + export STRIP="llvm-strip-14" + export AR="llvm-ar-14" + export RANLIB="llvm-ranlib-14" + EXTRA_CFLAGS="--target=aarch64-linux-gnu -I/usr/local/cuda_arm64/include $EXTRA_CFLAGS" + EXTRA_LDFLAGS="-fuse-ld=lld --target=aarch64-linux-gnu -L/usr/local/cuda_arm64/lib64 $EXTRA_LDFLAGS" + EXTRA_FFMPEG_FLAGS="$EXTRA_FFMPEG_FLAGS --arch=aarch64 --enable-cross-compile --cc=clang --strip=llvm-strip-14" + HOST_OS="--host=aarch64-linux-gnu" +fi + +if [[ "$BUILDARCH" == "arm64" && "$BUILDOS" == "darwin" && "$GOARCH" == "arm64" && "$GOOS" == "linux" ]]; then + SYSROOT="${SYSROOT:-"/tmp/sysroot-aarch64-linux-gnu"}" + check_sysroot + echo "cross-compiling darwin-arm64 --> linux-arm64" + LLVM_PATH="${LLVM_PATH:-/opt/homebrew/opt/llvm/bin}" + if [[ ! -f "$LLVM_PATH/ld.lld" ]]; then + echo "llvm linker not found at '$LLVM_PATH/ld.lld'. try 'brew install llvm' or set LLVM_PATH to your LLVM bin directory" + exit 1 + fi + export CC="$LLVM_PATH/clang --sysroot=$SYSROOT" + export AR="/opt/homebrew/opt/llvm/bin/llvm-ar" + export RANLIB="/opt/homebrew/opt/llvm/bin/llvm-ranlib" + EXTRA_CFLAGS="--target=aarch64-linux-gnu $EXTRA_CFLAGS" + EXTRA_LDFLAGS="--target=aarch64-linux-gnu -fuse-ld=$LLVM_PATH/ld.lld $EXTRA_LDFLAGS" + EXTRA_FFMPEG_FLAGS="$EXTRA_FFMPEG_FLAGS --arch=aarch64 --enable-cross-compile --cc=$LLVM_PATH/clang --sysroot=$SYSROOT --ar=$AR --ranlib=$RANLIB --target-os=linux" + EXTRA_X264_FLAGS="$EXTRA_X264_FLAGS --sysroot=$SYSROOT --ar=$AR --ranlib=$RANLIB" + HOST_OS="--host=aarch64-linux-gnu" +fi + +if [[ "$BUILDOS" == "linux" && "$GOARCH" == "amd64" && "$GOOS" == "windows" ]]; then + echo "cross-compiling linux-$BUILDARCH --> windows-amd64" + SYSROOT="${SYSROOT:-"/usr/x86_64-w64-mingw32"}" + check_sysroot + EXTRA_CFLAGS="-L$SYSROOT/lib -I$SYSROOT/include $EXTRA_CFLAGS" + EXTRA_LDFLAGS="-L$SYSROOT/lib $EXTRA_LDFLAGS" + EXTRA_FFMPEG_FLAGS="$EXTRA_FFMPEG_FLAGS --arch=x86_64 --enable-cross-compile --cross-prefix=x86_64-w64-mingw32- --target-os=mingw64 --sysroot=$SYSROOT" + EXTRA_X264_FLAGS="$EXTRA_X264_FLAGS --cross-prefix=x86_64-w64-mingw32- --sysroot=$SYSROOT" + HOST_OS="--host=mingw64" + # Workaround for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=967969 + export PKG_CONFIG_LIBDIR="/usr/local/x86_64-w64-mingw32/lib/pkgconfig" + EXTRA_FFMPEG_FLAGS="$EXTRA_FFMPEG_FLAGS --pkg-config=$(which pkg-config)" +fi + +if [[ "$BUILDARCH" == "amd64" && "$BUILDOS" == "darwin" && "$GOARCH" == "arm64" && "$GOOS" == "darwin" ]]; then + echo "cross-compiling darwin-amd64 --> darwin-arm64" + EXTRA_CFLAGS="$EXTRA_CFLAGS --target=arm64-apple-macos11" + EXTRA_LDFLAGS="$EXTRA_LDFLAGS --target=arm64-apple-macos11" + HOST_OS="--host=aarch64-darwin" + EXTRA_FFMPEG_FLAGS="$EXTRA_FFMPEG_FLAGS --arch=aarch64 --enable-cross-compile" +fi + +# Windows (MSYS2) needs a few tweaks +if [[ "$BUILDOS" == *"MSYS"* ]]; then + ROOT="/build" + export PATH="$PATH:/usr/bin:/mingw64/bin" + export C_INCLUDE_PATH="${C_INCLUDE_PATH:-}:/mingw64/lib" + + export PATH="$ROOT/compiled/bin":$PATH + export PKG_CONFIG_PATH=/mingw64/lib/pkgconfig + + export TARGET_OS="--target-os=mingw64" + export HOST_OS="--host=x86_64-w64-mingw32" + export BUILD_OS="--build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 --target=x86_64-w64-mingw32" + + # Needed for mbedtls + export WINDOWS_BUILD=1 +fi + +export PATH="$ROOT/compiled/bin:${PATH}" +export PKG_CONFIG_PATH="${PKG_CONFIG_PATH:-}:$ROOT/compiled/lib/pkgconfig" + +mkdir -p "$ROOT/" + +# NVENC only works on Windows/Linux +if [[ "$GOOS" != "darwin" ]]; then + if [[ ! -e "$ROOT/nv-codec-headers" ]]; then + git clone https://git.videolan.org/git/ffmpeg/nv-codec-headers.git "$ROOT/nv-codec-headers" + cd $ROOT/nv-codec-headers + git checkout n12.2.72.0 + make -e PREFIX="$ROOT/compiled" + make install -e PREFIX="$ROOT/compiled" + fi +fi + +if [[ "$GOOS" != "windows" && "$GOARCH" == "amd64" ]]; then + if [[ ! -e "$ROOT/nasm-2.14.02" ]]; then + # sudo apt-get -y install asciidoc xmlto # this fails :( + cd "$ROOT" + curl -o nasm-2.14.02.tar.gz https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.gz + echo 'b34bae344a3f2ed93b2ca7bf25f1ed3fb12da89eeda6096e3551fd66adeae9fc nasm-2.14.02.tar.gz' >nasm-2.14.02.tar.gz.sha256 + sha256sum -c nasm-2.14.02.tar.gz.sha256 + tar xf nasm-2.14.02.tar.gz + rm nasm-2.14.02.tar.gz nasm-2.14.02.tar.gz.sha256 + cd "$ROOT/nasm-2.14.02" + ./configure --prefix="$ROOT/compiled" + make -j$NPROC + make -j$NPROC install || echo "Installing docs fails but should be OK otherwise" + fi +fi + +if [[ ! -e "$ROOT/x264" ]]; then + git clone http://git.videolan.org/git/x264.git "$ROOT/x264" + cd "$ROOT/x264" + if [[ $GOARCH == "arm64" ]]; then + # newer git master, compiles on Apple Silicon + git checkout 66a5bc1bd1563d8227d5d18440b525a09bcf17ca + else + # older git master, does not compile on Apple Silicon + git checkout 545de2ffec6ae9a80738de1b2c8cf820249a2530 + fi + ./configure --prefix="$ROOT/compiled" --enable-pic --enable-static ${HOST_OS:-} --disable-cli --extra-cflags="$EXTRA_CFLAGS" --extra-asflags="$EXTRA_CFLAGS" --extra-ldflags="$EXTRA_LDFLAGS" $EXTRA_X264_FLAGS || (cat $ROOT/x264/config.log && exit 1) + make -j$NPROC + make -j$NPROC install-lib-static +fi + +if [[ "$GOOS" == "linux" && "$BUILD_TAGS" == *"debug-video"* ]]; then + sudo apt-get install -y libnuma-dev cmake + if [[ ! -e "$ROOT/x265" ]]; then + git clone https://bitbucket.org/multicoreware/x265_git.git "$ROOT/x265" + cd "$ROOT/x265" + git checkout 17839cc0dc5a389e27810944ae2128a65ac39318 + cd build/linux/ + cmake -DCMAKE_INSTALL_PREFIX=$ROOT/compiled -G "Unix Makefiles" ../../source + make -j$NPROC + make -j$NPROC install + fi + # VP8/9 support + if [[ ! -e "$ROOT/libvpx" ]]; then + git clone https://chromium.googlesource.com/webm/libvpx.git "$ROOT/libvpx" + cd "$ROOT/libvpx" + git checkout ab35ee100a38347433af24df05a5e1578172a2ae + ./configure --prefix="$ROOT/compiled" --disable-examples --disable-unit-tests --enable-vp9-highbitdepth --enable-shared --as=nasm + make -j$NPROC + make -j$NPROC install + fi +fi + +DISABLE_FFMPEG_COMPONENTS="" +EXTRA_FFMPEG_LDFLAGS="$EXTRA_LDFLAGS" +# all flags which should be present for production build, but should be replaced/removed for debug build +DEV_FFMPEG_FLAGS="" + +if [[ "$BUILDOS" == "darwin" && "$GOOS" == "darwin" ]]; then + EXTRA_FFMPEG_LDFLAGS="$EXTRA_FFMPEG_LDFLAGS -framework CoreFoundation -framework Security" +elif [[ "$GOOS" == "windows" ]]; then + EXTRA_FFMPEG_FLAGS="$EXTRA_FFMPEG_FLAGS --enable-cuda --enable-cuda-llvm --enable-cuvid --enable-nvenc --enable-decoder=h264_cuvid,hevc_cuvid,vp8_cuvid,vp9_cuvid --enable-filter=scale_cuda,signature_cuda,hwupload_cuda --enable-encoder=h264_nvenc,hevc_nvenc" +elif [[ -e "/usr/local/cuda/lib64" ]]; then + echo "CUDA SDK detected, building with GPU support" + EXTRA_FFMPEG_FLAGS="$EXTRA_FFMPEG_FLAGS --enable-nonfree --enable-cuda-nvcc --enable-libnpp --enable-cuda --enable-cuda-llvm --enable-cuvid --enable-nvenc --enable-decoder=h264_cuvid,hevc_cuvid,vp8_cuvid,vp9_cuvid --enable-filter=scale_npp,signature_cuda,hwupload_cuda --enable-encoder=h264_nvenc,hevc_nvenc" +else + echo "No CUDA SDK detected, building without GPU support" +fi + +if [[ $BUILD_TAGS == *"debug-video"* ]]; then + echo "video debug mode, building ffmpeg with tools, debug info and additional capabilities for running tests" + DEV_FFMPEG_FLAGS="--enable-muxer=md5,flv --enable-demuxer=hls --enable-filter=ssim,tinterlace --enable-encoder=wrapped_avframe,pcm_s16le " + DEV_FFMPEG_FLAGS+="--enable-shared --enable-debug=3 --disable-stripping --disable-optimizations --enable-encoder=libx265,libvpx_vp8,libvpx_vp9 " + DEV_FFMPEG_FLAGS+="--enable-decoder=hevc,libvpx_vp8,libvpx_vp9 --enable-libx265 --enable-libvpx --enable-bsf=noise " +else + # disable all unnecessary features for production build + DISABLE_FFMPEG_COMPONENTS+=" --disable-doc --disable-sdl2 --disable-iconv --disable-muxers --disable-demuxers --disable-parsers --disable-protocols " + DISABLE_FFMPEG_COMPONENTS+=" --disable-encoders --disable-decoders --disable-filters --disable-bsfs --disable-postproc --disable-lzma " +fi + +if [[ ! -e "$ROOT/ffmpeg/libavcodec/libavcodec.a" ]]; then + git clone https://github.com/livepeer/FFmpeg.git "$ROOT/ffmpeg" || echo "FFmpeg dir already exists" + cd "$ROOT/ffmpeg" + git checkout d9751c73e714b01b363483db358b1ea8022c9bea + ./configure ${TARGET_OS:-} $DISABLE_FFMPEG_COMPONENTS --fatal-warnings \ + --enable-libx264 --enable-gpl \ + --enable-protocol=rtmp,file,pipe \ + --enable-muxer=mpegts,hls,segment,mp4,hevc,matroska,webm,null --enable-demuxer=flv,mpegts,mp4,mov,webm,matroska \ + --enable-bsf=h264_mp4toannexb,aac_adtstoasc,h264_metadata,h264_redundant_pps,hevc_mp4toannexb,extract_extradata \ + --enable-parser=aac,aac_latm,h264,hevc,vp8,vp9 \ + --enable-filter=abuffer,buffer,abuffersink,buffersink,afifo,fifo,aformat,format \ + --enable-filter=aresample,asetnsamples,fps,scale,hwdownload,select,livepeer_dnn,signature \ + --enable-encoder=aac,opus,libx264 \ + --enable-decoder=aac,opus,h264 \ + --extra-cflags="${EXTRA_CFLAGS} -I${ROOT}/compiled/include -I/usr/local/cuda/include" \ + --extra-ldflags="${EXTRA_FFMPEG_LDFLAGS} -L${ROOT}/compiled/lib -L/usr/local/cuda/lib64" \ + --prefix="$ROOT/compiled" \ + $EXTRA_FFMPEG_FLAGS \ + $DEV_FFMPEG_FLAGS || (tail -100 ${ROOT}/ffmpeg/ffbuild/config.log && exit 1) + # If configure fails, then print the last 100 log lines for debugging and exit. +fi + +if [[ ! -e "$ROOT/ffmpeg/libavcodec/libavcodec.a" || $BUILD_TAGS == *"debug-video"* ]]; then + cd "$ROOT/ffmpeg" + make -j$NPROC + make -j$NPROC install +fi diff --git a/segmenter/video_segmenter_test.go b/segmenter/video_segmenter_test.go index 95d39fcae5..371f023e4b 100644 --- a/segmenter/video_segmenter_test.go +++ b/segmenter/video_segmenter_test.go @@ -482,7 +482,7 @@ func ffprobe_firstframeflags(fname string) (string, error) { func TestMissingKeyframe(t *testing.T) { // sanity check that test file has a keyframe at the beginning out, err := ffprobe_firstframeflags("test.flv") - if err != nil || out != "flags=K_" { + if err != nil || out != "flags=K__" { t.Errorf("First video packet of test file was not a keyframe '%v' - %v", out, err) return } @@ -506,7 +506,7 @@ func TestMissingKeyframe(t *testing.T) { // sanity check tempfile doesn't have a video keyframe at the beginning out, err = ffprobe_firstframeflags(fname) - if err != nil || out != "flags=__" { + if err != nil || out != "flags=___" { t.Errorf("First video packet of temp file unexpected; %v - %v", out, err) return } @@ -520,7 +520,7 @@ func TestMissingKeyframe(t *testing.T) { } // and now check that segmented result does have keyframe at beginning out, err = ffprobe_firstframeflags(path.Join(dir, "out_0.ts")) - if err != nil || out != "flags=K_" { + if err != nil || out != "flags=K__" { t.Errorf("Segment did not have keyframe at beginning %v - %v", out, err) return }