using std::map;
using std::shared_ptr;
using std::make_shared;
+using std::make_pair;
using boost::is_any_of;
using boost::split;
using boost::optional;
text.push_back (make_shared<TextDecoder>(this, c->only_text(), ContentTime()));
}
- _next_time.resize (_format_context->nb_streams);
+ for (auto i: c->ffmpeg_audio_streams()) {
+ _next_time[i] = boost::optional<dcpomatic::ContentTime>();
+ }
}
-void
+bool
FFmpegDecoder::flush ()
{
- /* Get any remaining frames */
+ /* Flush video and audio once */
- _packet.data = 0;
- _packet.size = 0;
-
- /* XXX: should we reset _packet.data and size after each *_decode_* call? */
+ bool did_something = false;
+ if (video) {
+ AVPacket packet;
+ av_init_packet (&packet);
+ packet.data = nullptr;
+ packet.size = 0;
+ if (decode_and_process_video_packet(&packet)) {
+ did_something = true;
+ }
+ }
- while (video && decode_video_packet()) {}
+ for (auto i: ffmpeg_content()->ffmpeg_audio_streams()) {
+ AVPacket packet;
+ av_init_packet (&packet);
+ packet.data = nullptr;
+ packet.size = 0;
+ auto result = decode_audio_packet (i, &packet);
+ if (result.second) {
+ process_audio_frame (i);
+ did_something = true;
+ }
+ }
- if (audio) {
- decode_audio_packet ();
+ if (did_something) {
+ /* We want to be called again */
+ return false;
}
/* Make sure all streams are the same length and round up to the next video frame */
if (audio) {
audio->flush ();
}
+
+ return true;
}
bool
FFmpegDecoder::pass ()
{
- int r = av_read_frame (_format_context, &_packet);
+ auto packet = av_packet_alloc();
+ DCPOMATIC_ASSERT (packet);
+
+ int r = av_read_frame (_format_context, packet);
/* AVERROR_INVALIDDATA can apparently be returned sometimes even when av_read_frame
has pretty-much succeeded (and hence generated data which should be processed).
LOG_ERROR (N_("error on av_read_frame (%1) (%2)"), &buf[0], r);
}
- flush ();
- return true;
+ av_packet_free (&packet);
+ return flush ();
}
- int const si = _packet.stream_index;
+ int const si = packet->stream_index;
auto fc = _ffmpeg_content;
if (_video_stream && si == _video_stream.get() && video && !video->ignore()) {
- decode_video_packet ();
+ decode_and_process_video_packet (packet);
} else if (fc->subtitle_stream() && fc->subtitle_stream()->uses_index(_format_context, si) && !only_text()->ignore()) {
- decode_subtitle_packet ();
+ decode_and_process_subtitle_packet (packet);
} else {
- decode_audio_packet ();
+ decode_and_process_audio_packet (packet);
}
- av_packet_unref (&_packet);
+ av_packet_free (&packet);
return false;
}
_have_current_subtitle = false;
for (auto& i: _next_time) {
- i = optional<ContentTime>();
+ i.second = boost::optional<dcpomatic::ContentTime>();
}
}
-void
-FFmpegDecoder::decode_audio_packet ()
+shared_ptr<FFmpegAudioStream>
+FFmpegDecoder::audio_stream_from_index (int index) const
{
- /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
- several times.
- */
-
- AVPacket copy_packet = _packet;
- int const stream_index = copy_packet.stream_index;
-
/* XXX: inefficient */
auto streams = ffmpeg_content()->ffmpeg_audio_streams();
- auto stream = streams.begin ();
- while (stream != streams.end () && !(*stream)->uses_index (_format_context, stream_index)) {
+ auto stream = streams.begin();
+ while (stream != streams.end() && !(*stream)->uses_index(_format_context, index)) {
++stream;
}
if (stream == streams.end ()) {
- /* The packet's stream may not be an audio one; just ignore it in this method if so */
+ return {};
+ }
+
+ return *stream;
+}
+
+
+void
+FFmpegDecoder::process_audio_frame (shared_ptr<FFmpegAudioStream> stream)
+{
+ auto data = deinterleave_audio (stream);
+
+ ContentTime ct;
+ if (_frame->pts == AV_NOPTS_VALUE) {
+ /* In some streams we see not every frame coming through with a timestamp; for those
+ that have AV_NOPTS_VALUE we need to work out the timestamp ourselves. This is
+ particularly noticeable with TrueHD streams (see #1111).
+ */
+ if (_next_time[stream]) {
+ ct = *_next_time[stream];
+ }
+ } else {
+ ct = ContentTime::from_seconds (
+ _frame->best_effort_timestamp *
+ av_q2d (stream->stream(_format_context)->time_base))
+ + _pts_offset;
+ }
+
+ _next_time[stream] = ct + ContentTime::from_frames(data->frames(), stream->frame_rate());
+
+ if (ct < ContentTime()) {
+ /* Discard audio data that comes before time 0 */
+ auto const remove = min (int64_t(data->frames()), (-ct).frames_ceil(double(stream->frame_rate())));
+ data->move (data->frames() - remove, remove, 0);
+ data->set_frames (data->frames() - remove);
+ ct += ContentTime::from_frames (remove, stream->frame_rate());
+ }
+
+ if (ct < ContentTime()) {
+ LOG_WARNING (
+ "Crazy timestamp %1 for %2 samples in stream %3 (ts=%4 tb=%5, off=%6)",
+ to_string(ct),
+ data->frames(),
+ stream->id(),
+ _frame->best_effort_timestamp,
+ av_q2d(stream->stream(_format_context)->time_base),
+ to_string(_pts_offset)
+ );
+ }
+
+ /* Give this data provided there is some, and its time is sane */
+ if (ct >= ContentTime() && data->frames() > 0) {
+ audio->emit (film(), stream, data, ct);
+ }
+}
+
+
+pair<int, bool>
+FFmpegDecoder::decode_audio_packet (shared_ptr<FFmpegAudioStream> stream, AVPacket* packet)
+{
+ int frame_finished;
+ DCPOMATIC_DISABLE_WARNINGS
+ int decode_result = avcodec_decode_audio4 (stream->stream(_format_context)->codec, _frame, &frame_finished, packet);
+ DCPOMATIC_ENABLE_WARNINGS
+ if (decode_result < 0) {
+ /* avcodec_decode_audio4 can sometimes return an error even though it has decoded
+ some valid data; for example dca_subframe_footer can return AVERROR_INVALIDDATA
+ if it overreads the auxiliary data. ffplay carries on if frame_finished is true,
+ even in the face of such an error, so I think we should too.
+
+ Returning from the method here caused mantis #352.
+ */
+ LOG_WARNING ("avcodec_decode_audio4 failed (%1)", decode_result);
+ }
+ return make_pair(decode_result, frame_finished);
+}
+
+
+void
+FFmpegDecoder::decode_and_process_audio_packet (AVPacket* packet)
+{
+ auto stream = audio_stream_from_index (packet->stream_index);
+ if (!stream) {
return;
}
-DCPOMATIC_DISABLE_WARNINGS
- while (copy_packet.size > 0) {
+ /* Audio packets can contain multiple frames, so we may have to call avcodec_decode_audio4
+ several times. Make a simple copy so we can alter data and size.
+ */
+ AVPacket copy_packet = *packet;
- int frame_finished;
- int decode_result = avcodec_decode_audio4 ((*stream)->stream (_format_context)->codec, _frame, &frame_finished, ©_packet);
- if (decode_result < 0) {
+ while (copy_packet.size > 0) {
+ auto result = decode_audio_packet (stream, ©_packet);
+ if (result.first < 0) {
/* avcodec_decode_audio4 can sometimes return an error even though it has decoded
some valid data; for example dca_subframe_footer can return AVERROR_INVALIDDATA
if it overreads the auxiliary data. ffplay carries on if frame_finished is true,
Returning from the method here caused mantis #352.
*/
- LOG_WARNING ("avcodec_decode_audio4 failed (%1)", decode_result);
-
- /* Fudge decode_result so that we come out of the while loop when
- we've processed this data.
- */
- decode_result = copy_packet.size;
}
- if (frame_finished) {
- shared_ptr<AudioBuffers> data = deinterleave_audio (*stream);
-
- ContentTime ct;
- if (_frame->pts == AV_NOPTS_VALUE) {
- /* In some streams we see not every frame coming through with a timestamp; for those
- that have AV_NOPTS_VALUE we need to work out the timestamp ourselves. This is
- particularly noticeable with TrueHD streams (see #1111).
- */
- if (_next_time[stream_index]) {
- ct = *_next_time[stream_index];
- }
- } else {
- ct = ContentTime::from_seconds (
- av_frame_get_best_effort_timestamp (_frame) *
- av_q2d ((*stream)->stream (_format_context)->time_base))
- + _pts_offset;
- }
-
- _next_time[stream_index] = ct + ContentTime::from_frames(data->frames(), (*stream)->frame_rate());
-
- if (ct < ContentTime()) {
- /* Discard audio data that comes before time 0 */
- auto const remove = min (int64_t (data->frames()), (-ct).frames_ceil(double((*stream)->frame_rate ())));
- data->move (data->frames() - remove, remove, 0);
- data->set_frames (data->frames() - remove);
- ct += ContentTime::from_frames (remove, (*stream)->frame_rate ());
- }
-
- if (ct < ContentTime()) {
- LOG_WARNING (
- "Crazy timestamp %1 for %2 samples in stream %3 packet pts %4 (ts=%5 tb=%6, off=%7)",
- to_string(ct),
- data->frames(),
- copy_packet.stream_index,
- copy_packet.pts,
- av_frame_get_best_effort_timestamp(_frame),
- av_q2d((*stream)->stream(_format_context)->time_base),
- to_string(_pts_offset)
- );
- }
-DCPOMATIC_ENABLE_WARNINGS
+ if (result.second) {
+ process_audio_frame (stream);
+ }
- /* Give this data provided there is some, and its time is sane */
- if (ct >= ContentTime() && data->frames() > 0) {
- audio->emit (film(), *stream, data, ct);
- }
+ if (result.first) {
+ break;
}
- copy_packet.data += decode_result;
- copy_packet.size -= decode_result;
+ copy_packet.data += result.first;
+ copy_packet.size -= result.first;
}
}
bool
-FFmpegDecoder::decode_video_packet ()
+FFmpegDecoder::decode_and_process_video_packet (AVPacket* packet)
{
DCPOMATIC_ASSERT (_video_stream);
int frame_finished;
DCPOMATIC_DISABLE_WARNINGS
- if (avcodec_decode_video2 (video_codec_context(), _frame, &frame_finished, &_packet) < 0 || !frame_finished) {
+ if (avcodec_decode_video2 (video_codec_context(), _frame, &frame_finished, packet) < 0 || !frame_finished) {
return false;
}
DCPOMATIC_ENABLE_WARNINGS
void
-FFmpegDecoder::decode_subtitle_packet ()
+FFmpegDecoder::decode_and_process_subtitle_packet (AVPacket* packet)
{
int got_subtitle;
AVSubtitle sub;
- if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
+ if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, packet) < 0 || !got_subtitle) {
return;
}
case SUBTITLE_NONE:
break;
case SUBTITLE_BITMAP:
- decode_bitmap_subtitle (rect, from);
+ process_bitmap_subtitle (rect, from);
break;
case SUBTITLE_TEXT:
cout << "XXX: SUBTITLE_TEXT " << rect->text << "\n";
break;
case SUBTITLE_ASS:
- decode_ass_subtitle (rect->ass, from);
+ process_ass_subtitle (rect->ass, from);
break;
}
}
void
-FFmpegDecoder::decode_bitmap_subtitle (AVSubtitleRect const * rect, ContentTime from)
+FFmpegDecoder::process_bitmap_subtitle (AVSubtitleRect const * rect, ContentTime from)
{
/* Note BGRA is expressed little-endian, so the first byte in the word is B, second
G, third R, fourth A.
void
-FFmpegDecoder::decode_ass_subtitle (string ass, ContentTime from)
+FFmpegDecoder::process_ass_subtitle (string ass, ContentTime from)
{
/* We have no styles and no Format: line, so I'm assuming that FFmpeg
produces a single format of Dialogue: lines...