Basics of FFmpeg examiner works.
authorCarl Hetherington <cth@carlh.net>
Fri, 21 Jun 2013 17:48:46 +0000 (18:48 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 25 Jun 2013 15:32:28 +0000 (16:32 +0100)
src/lib/ffmpeg.cc
src/lib/ffmpeg.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
test/ffmpeg_examiner_test.cc [new file with mode: 0644]
test/test.cc

index 0d897abfaf27a8e361e22c8ebe1da7ce05a7add5..a39de391af92abafbe3d9e39f355752875a13f72 100644 (file)
@@ -40,10 +40,6 @@ FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c)
        , _format_context (0)
        , _frame (0)
        , _video_stream (-1)
-       , _video_codec_context (0)
-       , _video_codec (0)
-       , _audio_codec_context (0)
-       , _audio_codec (0)
 {
        setup_general ();
        setup_video ();
@@ -53,13 +49,12 @@ FFmpeg::FFmpeg (boost::shared_ptr<const FFmpegContent> c)
 FFmpeg::~FFmpeg ()
 {
        boost::mutex::scoped_lock lm (_mutex);
-       
-       if (_audio_codec_context) {
-               avcodec_close (_audio_codec_context);
-       }
 
-       if (_video_codec_context) {
-               avcodec_close (_video_codec_context);
+       for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+               AVCodecContext* context = _format_context->streams[i]->codec;
+               if (context->codec_type == AVMEDIA_TYPE_VIDEO || context->codec_type == AVMEDIA_TYPE_AUDIO) {
+                       avcodec_close (context);
+               }
        }
 
        av_free (_frame);
@@ -104,14 +99,14 @@ FFmpeg::setup_video ()
 {
        boost::mutex::scoped_lock lm (_mutex);
        
-       _video_codec_context = _format_context->streams[_video_stream]->codec;
-       _video_codec = avcodec_find_decoder (_video_codec_context->codec_id);
+       AVCodecContext* context = _format_context->streams[_video_stream]->codec;
+       AVCodec* codec = avcodec_find_decoder (context->codec_id);
 
-       if (_video_codec == 0) {
+       if (codec == 0) {
                throw DecodeError (_("could not find video decoder"));
        }
 
-       if (avcodec_open2 (_video_codec_context, _video_codec, 0) < 0) {
+       if (avcodec_open2 (context, codec, 0) < 0) {
                throw DecodeError (N_("could not open video decoder"));
        }
 }
@@ -120,19 +115,33 @@ void
 FFmpeg::setup_audio ()
 {
        boost::mutex::scoped_lock lm (_mutex);
-       
-       if (!_ffmpeg_content->audio_stream ()) {
-               return;
+
+       for (uint32_t i = 0; i < _format_context->nb_streams; ++i) {
+               AVCodecContext* context = _format_context->streams[i]->codec;
+               if (context->codec_type != AVMEDIA_TYPE_AUDIO) {
+                       continue;
+               }
+               
+               AVCodec* codec = avcodec_find_decoder (context->codec_id);
+               if (codec == 0) {
+                       throw DecodeError (_("could not find audio decoder"));
+               }
+               
+               if (avcodec_open2 (context, codec, 0) < 0) {
+                       throw DecodeError (N_("could not open audio decoder"));
+               }
        }
+}
 
-       _audio_codec_context = _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
-       _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id);
 
-       if (_audio_codec == 0) {
-               throw DecodeError (_("could not find audio decoder"));
-       }
+AVCodecContext *
+FFmpeg::video_codec_context () const
+{
+       return _format_context->streams[_video_stream]->codec;
+}
 
-       if (avcodec_open2 (_audio_codec_context, _audio_codec, 0) < 0) {
-               throw DecodeError (N_("could not open audio decoder"));
-       }
+AVCodecContext *
+FFmpeg::audio_codec_context () const
+{
+       return _format_context->streams[_ffmpeg_content->audio_stream()->id]->codec;
 }
index dcafe17f759237945fc16f015d9111eb820b522f..4d1a45da3b9c866c48d6757523bfb4d18acf364d 100644 (file)
@@ -17,6 +17,9 @@
 
 */
 
+#ifndef DCPOMATIC_FFMPEG_H
+#define DCPOMATIC_FFMPEG_H
+
 #include <vector>
 #include <boost/shared_ptr.hpp>
 #include <boost/thread/mutex.hpp>
@@ -46,16 +49,16 @@ public:
        }
 
 protected:
+       AVCodecContext* video_codec_context () const;
+       AVCodecContext* audio_codec_context () const;
+       
        boost::shared_ptr<const FFmpegContent> _ffmpeg_content;
+
        AVFormatContext* _format_context;
        AVPacket _packet;
        AVFrame* _frame;
-       int _video_stream;
 
-       AVCodecContext* _video_codec_context;
-       AVCodec* _video_codec;
-       AVCodecContext* _audio_codec_context;    ///< may be 0 if there is no audio
-       AVCodec* _audio_codec;                   ///< may be 0 if there is no audio
+       int _video_stream;
 
        /* It would appear (though not completely verified) that one must have
           a mutex around calls to avcodec_open* and avcodec_close... and here
@@ -68,3 +71,5 @@ private:
        void setup_video ();
        void setup_audio ();
 };
+
+#endif
index 43e88c428948109d20d1189f208b9571a9924dae..68132c5abf94bea8aedd52196b8520712b0c1e8c 100644 (file)
@@ -76,6 +76,8 @@ FFmpegContent::FFmpegContent (shared_ptr<const Film> f, shared_ptr<const cxml::N
        for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) {
                _filters.push_back (Filter::from_id ((*i)->content ()));
        }
+
+       _first_video = node->optional_number_child<Time> ("FirstVideo");
 }
 
 FFmpegContent::FFmpegContent (FFmpegContent const & o)
@@ -119,6 +121,10 @@ FFmpegContent::as_xml (xmlpp::Node* node) const
        for (vector<Filter const *>::const_iterator i = _filters.begin(); i != _filters.end(); ++i) {
                node->add_child("Filter")->add_child_text ((*i)->id ());
        }
+
+       if (_first_video) {
+               node->add_child("FirstVideo")->add_child_text (lexical_cast<string> (_first_video.get ()));
+       }
 }
 
 void
@@ -135,7 +141,7 @@ FFmpegContent::examine (shared_ptr<Job> job)
 
        ContentVideoFrame video_length = 0;
        video_length = examiner->video_length ();
-       film->log()->log (String::compose ("Video length obtained from header as %1 frames", examiner->video_length ()));
+       film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
 
         {
                 boost::mutex::scoped_lock lm (_mutex);
@@ -151,6 +157,8 @@ FFmpegContent::examine (shared_ptr<Job> job)
                 if (!_audio_streams.empty ()) {
                         _audio_stream = _audio_streams.front ();
                 }
+
+               _first_video = examiner->first_video ();
         }
 
         take_from_video_examiner (examiner);
@@ -288,6 +296,7 @@ FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
        frame_rate = node->number_child<int> ("FrameRate");
        channels = node->number_child<int64_t> ("Channels");
        mapping = AudioMapping (node->node_child ("Mapping"));
+       start = node->optional_number_child<Time> ("Start");
 }
 
 void
@@ -297,6 +306,9 @@ FFmpegAudioStream::as_xml (xmlpp::Node* root) const
        root->add_child("Id")->add_child_text (lexical_cast<string> (id));
        root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
        root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
+       if (start) {
+               root->add_child("Start")->add_child_text (lexical_cast<string> (start));
+       }
        mapping.as_xml (root->add_child("Mapping"));
 }
 
index 98078ef3e279d573184d60adf9526af594a3b7e5..36c24c2b3353629614ada8999c0c1cb970b56674 100644 (file)
@@ -46,6 +46,7 @@ public:
         int frame_rate;
        int channels;
        AudioMapping mapping;
+       boost::optional<Time> start;
 };
 
 extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
@@ -139,6 +140,7 @@ private:
        boost::shared_ptr<FFmpegSubtitleStream> _subtitle_stream;
        std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
        boost::shared_ptr<FFmpegAudioStream> _audio_stream;
+       boost::optional<Time> _first_video;
        /** Video filters that should be used when generating DCPs */
        std::vector<Filter const *> _filters;
 };
index b1f8aa3d0ae6112733998ee0c7308ee62428fd04..f1d984ee15967aa1576426127769fc0bddb23da3 100644 (file)
@@ -242,11 +242,11 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
 AVSampleFormat
 FFmpegDecoder::audio_sample_format () const
 {
-       if (_audio_codec_context == 0) {
+       if (!_ffmpeg_content->audio_stream()) {
                return (AVSampleFormat) 0;
        }
        
-       return _audio_codec_context->sample_fmt;
+       return audio_codec_context()->sample_fmt;
 }
 
 int
@@ -290,7 +290,7 @@ FFmpegDecoder::do_seek (Time t, bool backwards, bool accurate)
        int64_t const vt = t / (av_q2d (_format_context->streams[_video_stream]->time_base) * TIME_HZ);
        av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
 
-       avcodec_flush_buffers (_video_codec_context);
+       avcodec_flush_buffers (video_codec_context());
        if (_subtitle_codec_context) {
                avcodec_flush_buffers (_subtitle_codec_context);
        }
@@ -306,7 +306,7 @@ FFmpegDecoder::do_seek (Time t, bool backwards, bool accurate)
                        
                        if (_packet.stream_index == _video_stream) {
                                int finished = 0;
-                               int const r = avcodec_decode_video2 (_video_codec_context, _frame, &finished, &_packet);
+                               int const r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
                                if (r >= 0 && finished) {
                                        int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
                                        if (bet > vt) {
@@ -334,7 +334,7 @@ FFmpegDecoder::decode_audio_packet ()
        while (copy_packet.size > 0) {
 
                int frame_finished;
-               int const decode_result = avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &copy_packet);
+               int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
                if (decode_result >= 0) {
                        if (frame_finished) {
                        
@@ -343,10 +343,10 @@ FFmpegDecoder::decode_audio_packet ()
                                        * av_frame_get_best_effort_timestamp(_frame);
                                
                                int const data_size = av_samples_get_buffer_size (
-                                       0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1
+                                       0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
                                        );
                                
-                               assert (_audio_codec_context->channels == _ffmpeg_content->audio_channels());
+                               assert (audio_codec_context()->channels == _ffmpeg_content->audio_channels());
                                audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds * TIME_HZ);
                        }
                        
@@ -360,7 +360,7 @@ bool
 FFmpegDecoder::decode_video_packet ()
 {
        int frame_finished;
-       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;
        }
                
@@ -416,11 +416,11 @@ FFmpegDecoder::decode_video_packet ()
 Time
 FFmpegDecoder::position () const
 {
-       if (_decode_video && _decode_audio && _audio_codec_context) {
+       if (_decode_video && _decode_audio && _ffmpeg_content->audio_stream()) {
                return min (_next_video, _next_audio);
        }
 
-       if (_decode_audio && _audio_codec_context) {
+       if (_decode_audio && _ffmpeg_content->audio_stream()) {
                return _next_audio;
        }
 
@@ -430,7 +430,9 @@ FFmpegDecoder::position () const
 bool
 FFmpegDecoder::done () const
 {
-       return (!_decode_audio || !_audio_codec_context || audio_done()) && (!_decode_video || video_done());
+       bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || audio_done();
+       bool const vd = !_decode_video || video_done();
+       return ad && vd;
 }
        
 void
index e5d356a27b7b213ffeaaa2a04f6e09f9c168ca94..c09395e76609086f33579b63b18c008cf4cc3744 100644 (file)
@@ -25,8 +25,10 @@ extern "C" {
 #include "ffmpeg_content.h"
 
 using std::string;
+using std::cout;
 using std::stringstream;
 using boost::shared_ptr;
+using boost::optional;
 
 FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
        : FFmpeg (c)
@@ -56,6 +58,61 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
                }
        }
 
+       /* Run through until we find the first audio (for each stream) and video */
+
+       while (1) {
+               int r = av_read_frame (_format_context, &_packet);
+               if (r < 0) {
+                       break;
+               }
+
+               int frame_finished;
+               avcodec_get_frame_defaults (_frame);
+
+               cout << "got packet " << _packet.stream_index << "\n";
+
+               AVCodecContext* context = _format_context->streams[_packet.stream_index]->codec;
+
+               if (_packet.stream_index == _video_stream && !_first_video) {
+                       if (avcodec_decode_video2 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+                               _first_video = frame_time (_video_stream);
+                       }
+               } else {
+                       for (size_t i = 0; i < _audio_streams.size(); ++i) {
+                               if (_packet.stream_index == _audio_streams[i]->id && !_audio_streams[i]->start) {
+                                       if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+                                               _audio_streams[i]->start = frame_time (_audio_streams[i]->id);
+                                       }
+                               }
+                       }
+               }
+
+               bool have_all_audio = true;
+               size_t i = 0;
+               while (i < _audio_streams.size() && have_all_audio) {
+                       have_all_audio = _audio_streams[i]->start;
+                       ++i;
+               }
+
+               if (_first_video && have_all_audio) {
+                       break;
+               }
+
+               av_free_packet (&_packet);
+       }
+}
+
+optional<Time>
+FFmpegExaminer::frame_time (int stream) const
+{
+       optional<Time> t;
+       
+       int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
+       if (bet != AV_NOPTS_VALUE) {
+               t = bet * av_q2d (_format_context->streams[stream]->time_base) * TIME_HZ;
+       }
+
+       return t;
 }
 
 float
@@ -73,7 +130,7 @@ FFmpegExaminer::video_frame_rate () const
 libdcp::Size
 FFmpegExaminer::video_size () const
 {
-       return libdcp::Size (_video_codec_context->width, _video_codec_context->height);
+       return libdcp::Size (video_codec_context()->width, video_codec_context()->height);
 }
 
 /** @return Length (in video frames) according to our content's header */
index 875451507a33d51833c9637784f63684b22fd0df..57b7775d4b84942c8aa065e891e8da230dcd5179 100644 (file)
@@ -17,6 +17,7 @@
 
 */
 
+#include <boost/optional.hpp>
 #include "ffmpeg.h"
 #include "video_examiner.h"
 
@@ -40,9 +41,15 @@ public:
                return _audio_streams;
        }
 
+       boost::optional<Time> first_video () const {
+               return _first_video;
+       }
+       
 private:
        std::string stream_name (AVStream* s) const;
+       boost::optional<Time> frame_time (int) const;
        
         std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
         std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
+       boost::optional<Time> _first_video;
 };
diff --git a/test/ffmpeg_examiner_test.cc b/test/ffmpeg_examiner_test.cc
new file mode 100644 (file)
index 0000000..6e73038
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "ffmpeg_examiner.h"
+
+BOOST_AUTO_TEST_CASE (ffmpeg_examiner_test)
+{
+       shared_ptr<Film> film = new_test_film ("ffmpeg_examiner_test");
+       shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd24.m2ts"));
+       shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (content));
+
+       BOOST_CHECK_EQUAL (examiner->first_video().get(), 57604000);
+       BOOST_CHECK_EQUAL (examiner->audio_streams().size(), 1);
+       BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->start.get(), 57600000);
+}
index 29a7fad940aad345cfbf8ff9300d1447e42166ab..3434a3f09af9c4a79384643f50dc6b06e43dddec 100644 (file)
@@ -154,6 +154,7 @@ check_dcp (string ref, string check)
        BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
 }
 
+#include "ffmpeg_examiner_test.cc"
 #include "black_fill_test.cc"
 #include "scaling_test.cc"
 #include "ratio_test.cc"
@@ -167,4 +168,3 @@ check_dcp (string ref, string check)
 #include "job_test.cc"
 #include "client_server_test.cc"
 #include "image_test.cc"
-