From: Carl Hetherington Date: Tue, 4 Feb 2014 09:59:38 +0000 (+0000) Subject: Merge master. X-Git-Tag: v2.0.48~919 X-Git-Url: https://main.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=4ba8772aef261da209bbb882325fd61a8b479fd7;hp=eec6f90d8e2c2246ce674ae13e4f460b12a4f2a9 Merge master. --- diff --git a/doc/design/resampling.tex b/doc/design/resampling.tex index 44aeee9b1..cf9cfb1ed 100644 --- a/doc/design/resampling.tex +++ b/doc/design/resampling.tex @@ -1,4 +1,5 @@ \documentclass{article} +\usepackage{amsmath} \begin{document} Here is what resampling we need to do. Content video is at $C_V$ fps, audio at $C_A$. @@ -18,6 +19,7 @@ $C_V$ is a DCI rate, $C_A$ is not. e.g.\ if $C_V = 24$, $C_A = 44.1\times{}10^3 \textbf{Resample $C_A$ to the DCI rate.} \section{Hard case 1} +\label{sec:hard1} $C_V$ is not a DCI rate, $C_A$ is, e.g.\ if $C_V = 25$, $C_A = 48\times{}10^3$. We will run the video at a nearby DCI rate $F_V$, @@ -31,5 +33,24 @@ resample audio to $25 * 48\times{}10^3 / 24 = 50\times{}10^3$. \medskip \textbf{Resample $C_A$ to $C_V C_A / F_V$} +\section{Hard case 2} + +Neither $C_V$ nor $C_A$ is not a DCI rate, e.g.\ if $C_V = 25$, $C_A = +44.1\times{}10^3$. We will run the video at a nearby DCI rate $F_V$, +meaning that it will run faster or slower than it should. We first +resample the audio to a DCI rate $F_A$, then perform as with +Section~\ref{sec:hard1} above. + +\medskip +\textbf{Resample $C_A$ to $C_V F_A / F_V$} + + +\section{The general case} + +Given a DCP running at $F_V$ and $F_A$ and a piece of content at $C_V$ +and $C_A$, resample the audio to $R_A$ where +\begin{align*} +R_A &= \frac{C_V F_A}{F_V} +\end{align*} \end{document} diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index 8186f9de4..872947b55 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -69,7 +69,7 @@ AnalyseAudioJob::run () _analysis.reset (new AudioAnalysis (_film->audio_channels ())); _done = 0; - OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ()); + AudioFrame const len = _film->time_to_audio_frames (_film->length ()); while (!player->pass ()) { set_progress (double (_done) / len); } @@ -81,7 +81,7 @@ AnalyseAudioJob::run () } void -AnalyseAudioJob::audio (shared_ptr b, Time) +AnalyseAudioJob::audio (shared_ptr b, DCPTime) { for (int i = 0; i < b->frames(); ++i) { for (int j = 0; j < b->channels(); ++j) { diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h index 3d4881983..6ed236d85 100644 --- a/src/lib/analyse_audio_job.h +++ b/src/lib/analyse_audio_job.h @@ -33,10 +33,10 @@ public: void run (); private: - void audio (boost::shared_ptr, Time); + void audio (boost::shared_ptr, DCPTime); boost::weak_ptr _content; - OutputAudioFrame _done; + AudioFrame _done; int64_t _samples_per_point; std::vector _current; diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index b4c4f34b6..3c0d13ba9 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -40,7 +40,7 @@ int const AudioContentProperty::AUDIO_GAIN = 203; int const AudioContentProperty::AUDIO_DELAY = 204; int const AudioContentProperty::AUDIO_MAPPING = 205; -AudioContent::AudioContent (shared_ptr f, Time s) +AudioContent::AudioContent (shared_ptr f, DCPTime s) : Content (f, s) , _audio_gain (0) , _audio_delay (Config::instance()->default_audio_delay ()) @@ -149,3 +149,27 @@ AudioContent::technical_summary () const { return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate()); } + +/** Note: this is not particularly fast, as the FrameRateChange lookup + * is not very intelligent. + * + * @param t Some duration to convert. + * @param at The time within the DCP to get the active frame rate change from; i.e. a point at which + * the `controlling' video content is active. + */ +AudioFrame +AudioContent::time_to_content_audio_frames (DCPTime t, DCPTime at) const +{ + shared_ptr film = _film.lock (); + assert (film); + + /* Consider the case where we're running a 25fps video at 24fps (i.e. slow) + Our audio is at 44.1kHz. We will resample it to 48000 * 25 / 24 and then + run it at 48kHz (i.e. slow, to match). + + After 1 second, we'll have run the equivalent of 44.1kHz * 24 / 25 samples + in the source. + */ + + return rint (t * content_audio_frame_rate() * film->active_frame_rate_change(at).speed_up / TIME_HZ); +} diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index ca4a1f234..0b2ee2e46 100644 --- a/src/lib/audio_content.h +++ b/src/lib/audio_content.h @@ -43,7 +43,7 @@ class AudioContent : public virtual Content public: typedef int64_t Frame; - AudioContent (boost::shared_ptr, Time); + AudioContent (boost::shared_ptr, DCPTime); AudioContent (boost::shared_ptr, boost::filesystem::path); AudioContent (boost::shared_ptr, boost::shared_ptr); AudioContent (boost::shared_ptr, std::vector >); @@ -52,7 +52,7 @@ public: std::string technical_summary () const; virtual int audio_channels () const = 0; - virtual AudioContent::Frame audio_length () const = 0; + virtual AudioFrame audio_length () const = 0; virtual int content_audio_frame_rate () const = 0; virtual int output_audio_frame_rate () const = 0; virtual AudioMapping audio_mapping () const = 0; @@ -74,6 +74,8 @@ public: return _audio_delay; } + Frame time_to_content_audio_frames (DCPTime, DCPTime) const; + private: /** Gain to apply to audio in dB */ float _audio_gain; diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index c0ef02f65..8d3b0e128 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -22,6 +22,8 @@ #include "exceptions.h" #include "log.h" #include "resampler.h" +#include "util.h" +#include "film.h" #include "i18n.h" @@ -35,24 +37,53 @@ using boost::shared_ptr; AudioDecoder::AudioDecoder (shared_ptr film, shared_ptr content) : Decoder (film) , _audio_content (content) - , _audio_position (0) { + if (content->output_audio_frame_rate() != content->content_audio_frame_rate() && content->audio_channels ()) { + _resampler.reset (new Resampler (content->content_audio_frame_rate(), content->output_audio_frame_rate(), content->audio_channels ())); + } +} + +/** Audio timestamping is made hard by many factors, but the final nail in the coffin is resampling. + * We have to assume that we are feeding continuous data into the resampler, and so we get continuous + * data out. Hence we do the timestamping here, post-resampler, just by counting samples. + * + * The time is passed in here so that after a seek we can set up our _audio_position. The + * time is ignored once this has been done. + */ +void +AudioDecoder::audio (shared_ptr data, ContentTime time) +{ + if (_resampler) { + data = _resampler->run (data); + } + if (!_audio_position) { + shared_ptr film = _film.lock (); + assert (film); + FrameRateChange frc = film->active_frame_rate_change (_audio_content->position ()); + _audio_position = (double (time) / frc.speed_up) * film->audio_frame_rate() / TIME_HZ; + } + + _pending.push_back (shared_ptr (new DecodedAudio (data, _audio_position.get ()))); + _audio_position = _audio_position.get() + data->frames (); } void -AudioDecoder::audio (shared_ptr data, AudioContent::Frame frame) +AudioDecoder::flush () { - Audio (data, frame); - _audio_position = frame + data->frames (); + if (!_resampler) { + return; + } + + shared_ptr b = _resampler->flush (); + if (b) { + _pending.push_back (shared_ptr (new DecodedAudio (b, _audio_position.get ()))); + _audio_position = _audio_position.get() + b->frames (); + } } -/** This is a bit odd, but necessary when we have (e.g.) FFmpegDecoders with no audio. - * The player needs to know that there is no audio otherwise it will keep trying to - * pass() the decoder to get it to emit audio. - */ -bool -AudioDecoder::has_audio () const +void +AudioDecoder::seek (ContentTime, bool) { - return _audio_content->audio_channels () > 0; + _audio_position.reset (); } diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index ab6c4b8a9..bb3aafccd 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -27,8 +27,10 @@ #include "decoder.h" #include "content.h" #include "audio_content.h" +#include "decoded.h" class AudioBuffers; +class Resampler; /** @class AudioDecoder. * @brief Parent class for audio decoders. @@ -37,17 +39,21 @@ class AudioDecoder : public virtual Decoder { public: AudioDecoder (boost::shared_ptr, boost::shared_ptr); + + boost::shared_ptr audio_content () const { + return _audio_content; + } - bool has_audio () const; - - /** Emitted when some audio data is ready */ - boost::signals2::signal, AudioContent::Frame)> Audio; - + void seek (ContentTime time, bool accurate); + protected: - void audio (boost::shared_ptr, AudioContent::Frame); + void audio (boost::shared_ptr, ContentTime); + void flush (); + boost::shared_ptr _audio_content; - AudioContent::Frame _audio_position; + boost::shared_ptr _resampler; + boost::optional _audio_position; }; #endif diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h index 226601e0e..f068b504e 100644 --- a/src/lib/audio_merger.h +++ b/src/lib/audio_merger.h @@ -37,6 +37,8 @@ public: TimedAudioBuffers pull (T time) { + assert (time >= _last_pull); + TimedAudioBuffers out; F const to_return = _t_to_f (time - _last_pull); @@ -97,9 +99,16 @@ public: if (_buffers->frames() == 0) { return TimedAudioBuffers (); } - + return TimedAudioBuffers (_buffers, _last_pull); } + + void + clear (DCPTime t) + { + _last_pull = t; + _buffers.reset (new AudioBuffers (_buffers->channels(), 0)); + } private: boost::shared_ptr _buffers; diff --git a/src/lib/content.cc b/src/lib/content.cc index ccca46bc0..ea1c19acd 100644 --- a/src/lib/content.cc +++ b/src/lib/content.cc @@ -54,7 +54,7 @@ Content::Content (shared_ptr f) } -Content::Content (shared_ptr f, Time p) +Content::Content (shared_ptr f, DCPTime p) : _film (f) , _position (p) , _trim_start (0) @@ -83,9 +83,9 @@ Content::Content (shared_ptr f, shared_ptr node) _paths.push_back ((*i)->content ()); } _digest = node->string_child ("Digest"); - _position = node->number_child