Obtain audio length by scanning through the file if required (#2880).
authorCarl Hetherington <cth@carlh.net>
Sat, 19 Oct 2024 19:29:54 +0000 (21:29 +0200)
committerCarl Hetherington <cth@carlh.net>
Fri, 1 Nov 2024 00:40:30 +0000 (01:40 +0100)
run/tests
src/lib/audio_stream.h
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
test/ffmpeg_audio_test.cc

index 5f356add3dfd77f7d0bc267685924ffe68453240..e1542c11407e3793293af6532b9dd1a41e1e3038 100755 (executable)
--- a/run/tests
+++ b/run/tests
@@ -3,7 +3,7 @@
 # e.g. --run_tests=foo
 set -e
 
-PRIVATE_GIT="881c48805e352dfe150993814757ca974282be18"
+PRIVATE_GIT="65c6d0cf0ec188e4d53fc8c9d61958c7921219d6"
 
 type=""
 check=0
index cf874242f10b494353a682f382bd32595e8c63c9..b125eb8c0663f11f1d11b38bd2a9b347063e155c 100644 (file)
@@ -54,6 +54,11 @@ public:
                return _length;
        }
 
+       void set_length(Frame length) {
+               boost::mutex::scoped_lock lm (_mutex);
+               _length = length;
+       }
+
        int channels () const;
        boost::optional<int> bit_depth() const;
 
index 31f1a3d64ff0ffc397b50c5dc0c0ad17148169c0..810a6eba23691a6fc8b0bbc417834dfce4e6f43b 100644 (file)
@@ -78,7 +78,10 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
                                s->codecpar->channel_layout = av_get_default_channel_layout (s->codecpar->channels);
                        }
 
-                       DCPOMATIC_ASSERT (_format_context->duration != AV_NOPTS_VALUE);
+                       if (_format_context->duration == AV_NOPTS_VALUE) {
+                               _need_audio_length = true;
+                       }
+
                        DCPOMATIC_ASSERT (codec->name);
 
                        _audio_streams.push_back (
@@ -87,7 +90,7 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
                                        codec->name,
                                        s->id,
                                        s->codecpar->sample_rate,
-                                       llrint ((double(_format_context->duration) / AV_TIME_BASE) * s->codecpar->sample_rate),
+                                       _need_audio_length ? 0 : rint ((double(_format_context->duration) / AV_TIME_BASE) * s->codecpar->sample_rate),
                                        s->codecpar->channels,
                                        s->codecpar->bits_per_raw_sample ? s->codecpar->bits_per_raw_sample : s->codecpar->bits_per_coded_sample
                                        )
@@ -114,6 +117,7 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
         *   - the first video.
         *   - the first audio for each stream.
         *   - the top-field-first and repeat-first-frame values ("temporal_reference") for the first PULLDOWN_CHECK_FRAMES video frames.
+        * or forever if _need_length is true.
         */
 
        int64_t const len = _file_group.length ();
@@ -122,6 +126,8 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
         * and a string seems a reasonably neat way to do that.
         */
        string temporal_reference;
+       bool carry_on_video = false;
+       std::vector<bool> carry_on_audio(_audio_streams.size());
        while (true) {
                auto packet = av_packet_alloc ();
                DCPOMATIC_ASSERT (packet);
@@ -141,27 +147,37 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
 
                auto context = _codec_context[packet->stream_index];
 
-               auto carry_on = false;
-
-               if (_video_stream && packet->stream_index == _video_stream.get()) {
-                       if (video_packet(context, temporal_reference, packet)) {
-                               carry_on = true;
+               boost::optional<size_t> audio_stream_index;
+               for (size_t i = 0; i < _audio_streams.size(); ++i) {
+                       if (_audio_streams[i]->uses_index(_format_context, packet->stream_index)) {
+                               audio_stream_index = i;
                        }
                }
 
-               for (size_t i = 0; i < _audio_streams.size(); ++i) {
-                       if (_audio_streams[i]->uses_index(_format_context, packet->stream_index)) {
-                               if (audio_packet(context, _audio_streams[i], packet)) {
-                                       carry_on = true;
-                               }
+               bool const video = _video_stream && packet->stream_index == *_video_stream;
+
+               if (!video && !audio_stream_index) {
+                       av_packet_free(&packet);
+                       continue;
+               }
+
+               if (video) {
+                       carry_on_video = video_packet(context, temporal_reference, packet);
+               }
+
+               if (audio_stream_index) {
+                       if (audio_packet(context, _audio_streams[*audio_stream_index], packet)) {
+                               carry_on_audio[*audio_stream_index] = true;
                        }
                }
 
                av_packet_free (&packet);
 
-               if (!carry_on) {
-                       /* All done */
-                       break;
+               if (!carry_on_video) {
+                       if (std::find(carry_on_audio.begin(), carry_on_audio.end(), true) == carry_on_audio.end()) {
+                               /* All done */
+                               break;
+                       }
                }
        }
 
@@ -262,7 +278,7 @@ FFmpegExaminer::video_packet (AVCodecContext* context, string& temporal_referenc
 bool
 FFmpegExaminer::audio_packet (AVCodecContext* context, shared_ptr<FFmpegAudioStream> stream, AVPacket* packet)
 {
-       if (stream->first_audio) {
+       if (stream->first_audio && !_need_audio_length) {
                return false;
        }
 
@@ -278,7 +294,14 @@ FFmpegExaminer::audio_packet (AVCodecContext* context, shared_ptr<FFmpegAudioStr
                return false;
        }
 
-       stream->first_audio = frame_time (frame, stream->stream(_format_context));
+       if (!stream->first_audio) {
+               stream->first_audio = frame_time(frame, stream->stream(_format_context));
+       }
+
+       if (_need_audio_length) {
+               stream->set_length(frame_time(frame, stream->stream(_format_context)).get_value_or({}).frames_round(stream->frame_rate()) + frame->nb_samples);
+       }
+
        return true;
 }
 
index f6fe8c42386b855f4460dac60262ee33bd573e65..57c97c542602cbe40889fe6d2630f3ee6e7a2b89 100644 (file)
@@ -103,6 +103,7 @@ private:
         */
        Frame _video_length = 0;
        bool _need_video_length = false;
+       bool _need_audio_length = false;
 
        boost::optional<double> _rotation;
        bool _pulldown = false;
index b2a83aad8a48e52e09b3db59159cdc133234a4ff..9ab7666c784fa2c7a22c9e17028f07d394a939c9 100644 (file)
@@ -140,3 +140,11 @@ BOOST_AUTO_TEST_CASE (ffmpeg_audio_test4)
        BOOST_CHECK_NO_THROW (while (!player->pass()) {});
 }
 
+
+
+BOOST_AUTO_TEST_CASE(no_audio_length_in_header)
+{
+       auto content = content_factory(TestPaths::private_data() / "10-seconds.thd");
+       auto film = new_test_film2("no_audio_length_in_header", content);
+       BOOST_CHECK(content[0]->full_length(film) == dcpomatic::DCPTime::from_seconds(10));
+}