X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Futil.cc;h=340b76b57bfaad0eb762ea98ba73a64497d351b8;hb=90550787a8b00329170a6a1d0e9bb58aeb92ac6f;hp=5e82650a56c6e29a5c00ff988cf1c493fe3e3b1b;hpb=05c37b9bb09f7bfa4c2ec8ea6b3fa4a83d0fec20;p=dcpomatic.git diff --git a/src/lib/util.cc b/src/lib/util.cc index 5e82650a5..340b76b57 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -26,6 +26,7 @@ #include #include #include +#include #ifdef DVDOMATIC_POSIX #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -56,14 +58,12 @@ extern "C" { #include "format.h" #include "dcp_content_type.h" #include "filter.h" -#include "screen.h" #include "sound_processor.h" -#ifndef DVDOMATIC_DISABLE_PLAYER -#include "player_manager.h" -#endif +#include "config.h" using namespace std; using namespace boost; +using libdcp::Size; thread::id ui_thread; @@ -191,34 +191,6 @@ stacktrace (ostream& out, int levels) } #endif -/** @return Version of vobcopy that is on the path (and hence that we will use) */ -static string -vobcopy_version () -{ - FILE* f = popen ("vobcopy -V 2>&1", "r"); - if (f == 0) { - throw EncodeError ("could not run vobcopy to check version"); - } - - string version = "unknown"; - - while (!feof (f)) { - char buf[256]; - if (fgets (buf, sizeof (buf), f)) { - string s (buf); - vector b; - split (b, s, is_any_of (" ")); - if (b.size() >= 2 && b[0] == "Vobcopy") { - version = b[1]; - } - } - } - - pclose (f); - - return version; -} - /** @param v Version as used by FFmpeg. * @return A string representation of v. */ @@ -236,7 +208,6 @@ dependency_version_summary () { stringstream s; s << "libopenjpeg " << opj_version () << ", " - << "vobcopy " << vobcopy_version() << ", " << "libavcodec " << ffmpeg_version_to_string (avcodec_version()) << ", " << "libavfilter " << ffmpeg_version_to_string (avfilter_version()) << ", " << "libavformat " << ffmpeg_version_to_string (avformat_version()) << ", " @@ -256,23 +227,14 @@ seconds (struct timeval t) return t.tv_sec + (double (t.tv_usec) / 1e6); } - -#ifdef DVDOMATIC_POSIX -void -sigchld_handler (int, siginfo_t* info, void *) -{ -#ifndef DVDOMATIC_DISABLE_PLAYER - PlayerManager::instance()->child_exited (info->si_pid); -#endif -} -#endif - /** Call the required functions to set up DVD-o-matic's static arrays, etc. * Must be called from the UI thread, if there is one. */ void dvdomatic_setup () { + avfilter_register_all (); + Format::setup_formats (); DCPContentType::setup_dcp_content_types (); Scaler::setup_scalers (); @@ -280,24 +242,23 @@ dvdomatic_setup () SoundProcessor::setup_sound_processors (); ui_thread = this_thread::get_id (); - -#ifdef DVDOMATIC_POSIX - struct sigaction sa; - sa.sa_flags = SA_SIGINFO; - sigemptyset (&sa.sa_mask); - sa.sa_sigaction = sigchld_handler; - sigaction (SIGCHLD, &sa, 0); -#endif } +/** @param start Start position for the crop within the image. + * @param size Size of the cropped area. + * @return FFmpeg crop filter string. + */ string -crop_string (Position start, Size size) +crop_string (Position start, libdcp::Size size) { stringstream s; s << "crop=" << size.width << ":" << size.height << ":" << start.x << ":" << start.y; return s.str (); } +/** @param s A string. + * @return Parts of the string split at spaces, except when a space is within quotation marks. + */ vector split_at_spaces_considering_quotes (string s) { @@ -374,22 +335,100 @@ md5_digest (string file) return s.str (); } -DCPFrameRate -dcp_frame_rate (float fps) +static bool about_equal (float a, float b) +{ + /* A film of F seconds at f FPS will be Ff frames; + Consider some delta FPS d, so if we run the same + film at (f + d) FPS it will last F(f + d) seconds. + + Hence the difference in length over the length of the film will + be F(f + d) - Ff frames + = Ff + Fd - Ff frames + = Fd frames + = Fd/f seconds + + So if we accept a difference of 1 frame, ie 1/f seconds, we can + say that + + 1/f = Fd/f + ie 1 = Fd + ie d = 1/F + + So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable + FPS error is 1/F ~= 0.0001 ~= 10-e4 + */ + + return (fabs (a - b) < 1e-4); +} + +class FrameRateCandidate { - DCPFrameRate dfr; +public: + FrameRateCandidate (float source_, int dcp_) + : source (source_) + , dcp (dcp_) + {} + + bool skip () const { + return !about_equal (source, dcp) && source > dcp; + } + + bool repeat () const { + return !about_equal (source, dcp) && source < dcp; + } + + float source; + int dcp; +}; + +/** @param fps Arbitrary source frames-per-second value */ +/** XXX: this could be slow-ish */ +DCPFrameRate::DCPFrameRate (float source_fps) +{ + list const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates (); + + /* Work out what rates we could manage, including those achieved by using skip / repeat. */ + list candidates; + + /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */ + for (list::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { + candidates.push_back (FrameRateCandidate (*i, *i)); + } + + /* Then the skip/repeat ones */ + for (list::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { + candidates.push_back (FrameRateCandidate (float (*i) / 2, *i)); + candidates.push_back (FrameRateCandidate (float (*i) * 2, *i)); + } + + /* Pick the best one, bailing early if we hit an exact match */ + float error = numeric_limits::max (); + boost::optional best; + list::iterator i = candidates.begin(); + while (i != candidates.end()) { + + if (about_equal (i->source, source_fps)) { + best = *i; + break; + } - dfr.run_fast = (fps != rint (fps)); - dfr.frames_per_second = rint (fps); - dfr.skip = 1; + float const e = fabs (i->source - source_fps); + if (e < error) { + error = e; + best = *i; + } + + ++i; + } - /* XXX: somewhat arbitrary */ - if (fps == 50) { - dfr.frames_per_second = 25; - dfr.skip = 2; + if (!best) { + throw EncodeError ("cannot find a suitable DCP frame rate for this source"); } - return dfr; + frames_per_second = best->dcp; + skip = best->skip (); + repeat = best->repeat (); + change_speed = !about_equal (source_fps * factor(), frames_per_second); } /** @param An arbitrary sampling rate. @@ -405,9 +444,17 @@ dcp_audio_sample_rate (int fs) return 96000; } -bool operator== (Size const & a, Size const & b) +int +dcp_audio_channels (int f) { - return (a.width == b.width && a.height == b.height); + if (f == 1) { + /* The source is mono, so to put the mono channel into + the centre we need to generate a 5.1 soundtrack. + */ + return 6; + } + + return f; } bool operator== (Crop const & a, Crop const & b) @@ -464,6 +511,7 @@ Socket::check () void Socket::connect (asio::ip::basic_resolver_entry const & endpoint, int timeout) { + _deadline.expires_from_now (posix_time::seconds (timeout)); system::error_code ec = asio::error::would_block; _socket.async_connect (endpoint, lambda::var(ec) = lambda::_1); do { @@ -574,6 +622,9 @@ Socket::read_definite_and_consume (uint8_t* data, int size, int timeout) /** Read as much data as is available, up to some limit. * @param data Where to put the data. * @param size Maximum amount of data to read. + * + * XXX This method assumes that there is always lots of data to read(); + * if there isn't, it will hang waiting for data that will never arrive. */ void Socket::read_indefinite (uint8_t* data, int size, int timeout) @@ -600,6 +651,9 @@ Socket::read_indefinite (uint8_t* data, int size, int timeout) memcpy (data, _buffer, size); } +/** @param other A Rect. + * @return The intersection of this with `other'. + */ Rect Rect::intersection (Rect const & other) const { @@ -614,11 +668,11 @@ Rect::intersection (Rect const & other) const } /** Round a number up to the nearest multiple of another number. - * @param a Number to round. + * @param c Index. + * @param s Array of numbers to round, indexed by c. * @param t Multiple to round to. * @return Rounded number. */ - int stride_round_up (int c, int const * stride, int t) { @@ -649,7 +703,7 @@ read_key_value (istream &s) if (line.empty ()) { continue; } - + if (line[0] == '#') { continue; } @@ -729,6 +783,10 @@ get_optional_int (multimap const & kv, string k) return lexical_cast (i->second); } +/** Construct an AudioBuffers. Audio data is undefined after this constructor. + * @param channels Number of channels. + * @param frames Number of frames to reserve space for. + */ AudioBuffers::AudioBuffers (int channels, int frames) : _channels (channels) , _frames (frames) @@ -740,6 +798,9 @@ AudioBuffers::AudioBuffers (int channels, int frames) } } +/** Copy constructor. + * @param other Other AudioBuffers; data is copied. + */ AudioBuffers::AudioBuffers (AudioBuffers const & other) : _channels (other._channels) , _frames (other._frames) @@ -752,6 +813,7 @@ AudioBuffers::AudioBuffers (AudioBuffers const & other) } } +/** AudioBuffers destructor */ AudioBuffers::~AudioBuffers () { for (int i = 0; i < _channels; ++i) { @@ -761,13 +823,20 @@ AudioBuffers::~AudioBuffers () delete[] _data; } +/** @param c Channel index. + * @return Buffer for this channel. + */ float* AudioBuffers::data (int c) const { assert (c >= 0 && c < _channels); return _data[c]; } - + +/** Set the number of frames that these AudioBuffers will report themselves + * as having. + * @param f Frames; must be less than or equal to the number of allocated frames. + */ void AudioBuffers::set_frames (int f) { @@ -775,26 +844,54 @@ AudioBuffers::set_frames (int f) _frames = f; } +/** Make all samples on all channels silent */ void AudioBuffers::make_silent () { for (int i = 0; i < _channels; ++i) { - for (int j = 0; j < _frames; ++j) { - _data[i][j] = 0; - } + make_silent (i); } } +/** Make all samples on a given channel silent. + * @param c Channel. + */ +void +AudioBuffers::make_silent (int c) +{ + assert (c >= 0 && c < _channels); + + for (int i = 0; i < _frames; ++i) { + _data[c][i] = 0; + } +} + +/** Copy data from another AudioBuffers to this one. All channels are copied. + * @param from AudioBuffers to copy from; must have the same number of channels as this. + * @param frames_to_copy Number of frames to copy. + * @param read_offset Offset to read from in `from'. + * @param write_offset Offset to write to in `to'. + */ void AudioBuffers::copy_from (AudioBuffers* from, int frames_to_copy, int read_offset, int write_offset) { assert (from->channels() == channels()); + assert (from); + assert (read_offset >= 0 && (read_offset + frames_to_copy) <= from->_allocated_frames); + assert (write_offset >= 0 && (write_offset + frames_to_copy) <= _allocated_frames); + for (int i = 0; i < _channels; ++i) { memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float)); } } +/** Move audio data around. + * @param from Offset to move from. + * @param to Offset to move to. + * @param frames Number of frames to move. + */ + void AudioBuffers::move (int from, int to, int frames) { @@ -816,14 +913,59 @@ AudioBuffers::move (int from, int to, int frames) } } +/** Trip an assert if the caller is not in the UI thread */ void ensure_ui_thread () { assert (this_thread::get_id() == ui_thread); } +/** @param v Source video frame. + * @param audio_sample_rate Source audio sample rate. + * @param frames_per_second Number of video frames per second. + * @return Equivalent number of audio frames for `v'. + */ int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second) { return ((int64_t) v * audio_sample_rate / frames_per_second); } + +/** @param f Filename. + * @return true if this file is a still image, false if it is something else. + */ +bool +still_image_file (string f) +{ + string ext = boost::filesystem::path(f).extension().string(); + + transform (ext.begin(), ext.end(), ext.begin(), ::tolower); + + return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png"); +} + +/** @return A pair containing CPU model name and the number of processors */ +pair +cpu_info () +{ + pair info; + info.second = 0; + +#ifdef DVDOMATIC_POSIX + ifstream f ("/proc/cpuinfo"); + while (f.good ()) { + string l; + getline (f, l); + if (boost::algorithm::starts_with (l, "model name")) { + string::size_type const c = l.find (':'); + if (c != string::npos) { + info.first = l.substr (c + 2); + } + } else if (boost::algorithm::starts_with (l, "processor")) { + ++info.second; + } + } +#endif + + return info; +}