X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Fffmpeg_decoder.cc;h=17973d0004126cf1d9564c4c1318ced61c6a6263;hb=44a4ff40df50688286e6db8b1fc2c024f6e5834b;hp=27b7aa7b7514e4cdcc20d9c2c79c0dd87cf873a5;hpb=94eff17bdb94c170d456c7dab5ad3d4c00af4b14;p=dcpomatic.git diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index 27b7aa7b7..17973d000 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -106,11 +106,41 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr film, shared_ptr(_flush_state)); + + switch (_flush_state) { + case FlushState::CODECS: + if (flush_codecs() == FlushResult::DONE) { + LOG_DEBUG_PLAYER_NC("Finished flushing codecs"); + _flush_state = FlushState::AUDIO_DECODER; + } + break; + case FlushState::AUDIO_DECODER: + if (audio) { + audio->flush(); + } + LOG_DEBUG_PLAYER_NC("Finished flushing audio decoder"); + _flush_state = FlushState::FILL; + break; + case FlushState::FILL: + if (flush_fill() == FlushResult::DONE) { + LOG_DEBUG_PLAYER_NC("Finished flushing fills"); + return FlushResult::DONE; + } + break; + } + + return FlushResult::AGAIN; +} + +/** @return true if we have finished flushing the codecs */ +FFmpegDecoder::FlushResult +FFmpegDecoder::flush_codecs() +{ bool did_something = false; if (video) { if (decode_and_process_video_packet(nullptr)) { @@ -132,48 +162,48 @@ FFmpegDecoder::flush () } } - if (did_something) { - /* We want to be called again */ - return false; - } + return did_something ? FlushResult::AGAIN : FlushResult::DONE; +} + +FFmpegDecoder::FlushResult +FFmpegDecoder::flush_fill() +{ /* Make sure all streams are the same length and round up to the next video frame */ + bool did_something = false; + auto const frc = film()->active_frame_rate_change(_ffmpeg_content->position()); ContentTime full_length (_ffmpeg_content->full_length(film()), frc); full_length = full_length.ceil (frc.source); - if (video) { + if (video && !video->ignore()) { double const vfr = _ffmpeg_content->video_frame_rate().get(); - auto const f = full_length.frames_round (vfr); - auto v = video->position(film()).get_value_or(ContentTime()).frames_round(vfr) + 1; - while (v < f) { - video->emit (film(), make_shared(_black_image), v); - ++v; + auto const v = video->position(film()).get_value_or(ContentTime()) + ContentTime::from_frames(1, vfr); + if (v < full_length) { + video->emit(film(), make_shared(_black_image), v); + did_something = true; } } - for (auto i: _ffmpeg_content->ffmpeg_audio_streams ()) { - auto a = audio->stream_position(film(), i); - /* Unfortunately if a is 0 that really means that we don't know the stream position since - there has been no data on it since the last seek. In this case we'll just do nothing - here. I'm not sure if that's the right idea. - */ - if (a > ContentTime()) { - while (a < full_length) { + if (audio && !audio->ignore()) { + for (auto i: _ffmpeg_content->ffmpeg_audio_streams ()) { + auto const a = audio->stream_position(film(), i); + /* Unfortunately if a is 0 that really means that we don't know the stream position since + there has been no data on it since the last seek. In this case we'll just do nothing + here. I'm not sure if that's the right idea. + */ + if (a > ContentTime() && a < full_length) { + LOG_DEBUG_PLAYER("Flush inserts silence at %1", to_string(a)); auto to_do = min (full_length - a, ContentTime::from_seconds (0.1)); auto silence = make_shared(i->channels(), to_do.frames_ceil (i->frame_rate())); silence->make_silent (); audio->emit (film(), i, silence, a, true); - a += to_do; + did_something = true; } } } - if (audio) { - audio->flush (); - } - - return true; + return did_something ? FlushResult::AGAIN : FlushResult::DONE; } @@ -190,6 +220,7 @@ FFmpegDecoder::pass () Hence it makes sense to continue here in that case. */ if (r < 0 && r != AVERROR_INVALIDDATA) { + LOG_DEBUG_PLAYER("FFpmegDecoder::pass flushes because av_read_frame returned %1", r); if (r != AVERROR_EOF) { /* Maybe we should fail here, but for now we'll just finish off instead */ char buf[256]; @@ -198,7 +229,7 @@ FFmpegDecoder::pass () } av_packet_free (&packet); - return flush (); + return flush() == FlushResult::DONE; } int const si = packet->stream_index; @@ -222,18 +253,22 @@ FFmpegDecoder::pass () */ static shared_ptr -deinterleave_audio(shared_ptr stream, AVFrame* frame) +deinterleave_audio(AVFrame* frame) { auto format = static_cast(frame->format); /* XXX: can't we use swr_convert() to do the format conversion? */ - int const channels = frame->channels; + int const channels = frame->ch_layout.nb_channels; int const frames = frame->nb_samples; int const total_samples = frames * channels; auto audio = make_shared(channels, frames); auto data = audio->data(); + if (frames == 0) { + return audio; + } + switch (format) { case AV_SAMPLE_FMT_U8: { @@ -328,14 +363,9 @@ deinterleave_audio(shared_ptr stream, AVFrame* frame) case AV_SAMPLE_FMT_FLTP: { auto p = reinterpret_cast (frame->data); - DCPOMATIC_ASSERT(channels <= stream->channels()); - /* Sometimes there aren't as many channels in the frame as in the stream */ for (int i = 0; i < channels; ++i) { memcpy (data[i], p[i], frames * sizeof(float)); } - for (int i = channels; i < stream->channels(); ++i) { - audio->make_silent (i); - } } break; @@ -449,7 +479,7 @@ void FFmpegDecoder::process_audio_frame (shared_ptr stream) { auto frame = audio_frame (stream); - auto data = deinterleave_audio(stream, frame); + auto data = deinterleave_audio(frame); auto const time_base = stream->stream(_format_context)->time_base; @@ -591,7 +621,7 @@ FFmpegDecoder::process_video_frame () video->emit ( film(), make_shared(image), - llrint(pts * _ffmpeg_content->active_video_frame_rate(film())) + ContentTime::from_seconds(pts) ); } else { LOG_WARNING_NC ("Dropping frame without PTS"); @@ -603,9 +633,14 @@ FFmpegDecoder::process_video_frame () void FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet) { + auto context = subtitle_codec_context(); + if (!context) { + return; + } + int got_subtitle; AVSubtitle sub; - if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, packet) < 0 || !got_subtitle) { + if (avcodec_decode_subtitle2(context, &sub, &got_subtitle, packet) < 0 || !got_subtitle) { return; } @@ -632,11 +667,11 @@ FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet) */ ContentTime from; from = sub_period.from + _pts_offset; + _have_current_subtitle = true; if (sub_period.to) { _current_subtitle_to = *sub_period.to + _pts_offset; } else { _current_subtitle_to = optional(); - _have_current_subtitle = true; } ContentBitmapText bitmap_text(from); @@ -684,7 +719,7 @@ FFmpegDecoder::process_bitmap_subtitle (AVSubtitleRect const * rect) /* sub_p looks up into a BGRA palette which is at rect->pict.data[1]; (i.e. first byte B, second G, third R, fourth A) */ - auto const palette = rect->pict.data[1]; + auto const* palette = rect->pict.data[1]; #else /* Start of the first line in the subtitle */ auto sub_p = rect->data[0]; @@ -745,11 +780,23 @@ FFmpegDecoder::process_bitmap_subtitle (AVSubtitleRect const * rect) if (target_height == 0 && video_codec_context()) { target_height = video_codec_context()->height; } - DCPOMATIC_ASSERT (target_width); - DCPOMATIC_ASSERT (target_height); + + int x_offset = 0; + int y_offset = 0; + if (_ffmpeg_content->video && _ffmpeg_content->video->use()) { + auto const crop = _ffmpeg_content->video->actual_crop(); + target_width -= crop.left + crop.right; + target_height -= crop.top + crop.bottom; + x_offset = -crop.left; + y_offset = -crop.top; + } + + DCPOMATIC_ASSERT(target_width > 0); + DCPOMATIC_ASSERT(target_height > 0); + dcpomatic::Rect const scaled_rect ( - static_cast(rect->x) / target_width, - static_cast(rect->y) / target_height, + static_cast(rect->x + x_offset) / target_width, + static_cast(rect->y + y_offset) / target_height, static_cast(rect->w) / target_width, static_cast(rect->h) / target_height ); @@ -780,11 +827,14 @@ FFmpegDecoder::process_ass_subtitle (string ass, ContentTime from) } sub::RawSubtitle base; + auto video_size = _ffmpeg_content->video->size(); + DCPOMATIC_ASSERT(video_size); + auto raw = sub::SSAReader::parse_line ( base, text, - _ffmpeg_content->video->size().width, - _ffmpeg_content->video->size().height, + video_size->width, + video_size->height, sub::Colour(1, 1, 1) );