Sync video stream to audio stream.

This commit is contained in:
John Preston 2019-02-21 15:15:44 +04:00
parent ec9512899e
commit 99d05ba967
9 changed files with 113 additions and 39 deletions

View file

@ -441,7 +441,7 @@ void Mixer::Track::clear() {
setVideoData(nullptr); setVideoData(nullptr);
lastUpdateWhen = 0; lastUpdateWhen = 0;
lastUpdateCorrectedMs = 0; lastUpdatePosition = 0;
} }
void Mixer::Track::started() { void Mixer::Track::started() {
@ -782,7 +782,7 @@ void Mixer::play(
current->state.id = audio; current->state.id = audio;
current->lastUpdateWhen = 0; current->lastUpdateWhen = 0;
current->lastUpdateCorrectedMs = 0; current->lastUpdatePosition = 0;
if (videoData) { if (videoData) {
current->setVideoData(std::move(videoData)); current->setVideoData(std::move(videoData));
} else { } else {
@ -823,6 +823,23 @@ void Mixer::feedFromVideo(const VideoSoundPart &part) {
_loader->feedFromVideo(part); _loader->feedFromVideo(part);
} }
Mixer::TimeCorrection Mixer::getVideoTimeCorrection(
const AudioMsgId &audio) const {
Expects(audio.type() == AudioMsgId::Type::Video);
Expects(audio.playId() != 0);
auto result = TimeCorrection();
const auto playId = audio.playId();
QMutexLocker lock(&AudioMutex);
const auto track = trackForType(AudioMsgId::Type::Video);
if (track->state.id.playId() == playId && track->lastUpdateWhen > 0) {
result.audioPositionValue = track->lastUpdatePosition;
result.audioPositionTime = track->lastUpdateWhen;
}
return result;
}
crl::time Mixer::getVideoCorrectedTime(const AudioMsgId &audio, crl::time frameMs, crl::time systemMs) { crl::time Mixer::getVideoCorrectedTime(const AudioMsgId &audio, crl::time frameMs, crl::time systemMs) {
auto result = frameMs; auto result = frameMs;
@ -830,7 +847,7 @@ crl::time Mixer::getVideoCorrectedTime(const AudioMsgId &audio, crl::time frameM
auto type = audio.type(); auto type = audio.type();
auto track = trackForType(type); auto track = trackForType(type);
if (track && track->state.id == audio && track->lastUpdateWhen > 0) { if (track && track->state.id == audio && track->lastUpdateWhen > 0) {
result = static_cast<crl::time>(track->lastUpdateCorrectedMs); result = static_cast<crl::time>(track->lastUpdatePosition);
if (systemMs > track->lastUpdateWhen) { if (systemMs > track->lastUpdateWhen) {
result += (systemMs - track->lastUpdateWhen); result += (systemMs - track->lastUpdateWhen);
} }
@ -848,7 +865,7 @@ void Mixer::videoSoundProgress(const AudioMsgId &audio) {
if (current && current->state.length && current->state.frequency) { if (current && current->state.length && current->state.frequency) {
if (current->state.id == audio && current->state.state == State::Playing) { if (current->state.id == audio && current->state.state == State::Playing) {
current->lastUpdateWhen = crl::now(); current->lastUpdateWhen = crl::now();
current->lastUpdateCorrectedMs = (current->state.position * 1000ULL) / current->state.frequency; current->lastUpdatePosition = (current->state.position * 1000ULL) / current->state.frequency;
} }
} }
} }
@ -906,7 +923,7 @@ void Mixer::pause(const AudioMsgId &audio, bool fast) {
emit faderOnTimer(); emit faderOnTimer();
track->lastUpdateWhen = 0; track->lastUpdateWhen = 0;
track->lastUpdateCorrectedMs = 0; track->lastUpdatePosition = 0;
} }
if (current) emit updated(current); if (current) emit updated(current);
} }

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "media/streaming/media_streaming_common.h"
#include "storage/localimageloader.h" #include "storage/localimageloader.h"
#include "base/bytes.h" #include "base/bytes.h"
@ -121,6 +122,15 @@ public:
// Video player audio stream interface. // Video player audio stream interface.
void feedFromVideo(const VideoSoundPart &part); void feedFromVideo(const VideoSoundPart &part);
struct TimeCorrection {
crl::time audioPositionValue = kTimeUnknown;
crl::time audioPositionTime = kTimeUnknown;
explicit operator bool() const {
return (audioPositionValue != kTimeUnknown);
}
};
TimeCorrection getVideoTimeCorrection(const AudioMsgId &audio) const;
crl::time getVideoCorrectedTime( crl::time getVideoCorrectedTime(
const AudioMsgId &id, const AudioMsgId &id,
crl::time frameMs, crl::time frameMs,
@ -228,7 +238,7 @@ private:
}; };
std::unique_ptr<SpeedEffect> speedEffect; std::unique_ptr<SpeedEffect> speedEffect;
crl::time lastUpdateWhen = 0; crl::time lastUpdateWhen = 0;
crl::time lastUpdateCorrectedMs = 0; crl::time lastUpdatePosition = 0;
private: private:
void createStream(AudioMsgId::Type type); void createStream(AudioMsgId::Type type);

View file

@ -18,14 +18,17 @@ namespace Streaming {
AudioTrack::AudioTrack( AudioTrack::AudioTrack(
const PlaybackOptions &options, const PlaybackOptions &options,
Stream &&stream, Stream &&stream,
AudioMsgId audioId,
FnMut<void(const Information &)> ready, FnMut<void(const Information &)> ready,
Fn<void()> error) Fn<void()> error)
: _options(options) : _options(options)
, _stream(std::move(stream)) , _stream(std::move(stream))
, _audioId(audioId)
, _ready(std::move(ready)) , _ready(std::move(ready))
, _error(std::move(error)) { , _error(std::move(error)) {
Expects(_ready != nullptr); Expects(_ready != nullptr);
Expects(_error != nullptr); Expects(_error != nullptr);
Expects(_audioId.playId() != 0);
} }
int AudioTrack::streamIndex() const { int AudioTrack::streamIndex() const {
@ -39,13 +42,17 @@ AVRational AudioTrack::streamTimeBase() const {
void AudioTrack::process(Packet &&packet) { void AudioTrack::process(Packet &&packet) {
_noMoreData = packet.empty(); _noMoreData = packet.empty();
if (_audioMsgId.playId()) { if (initialized()) {
mixerEnqueue(std::move(packet)); mixerEnqueue(std::move(packet));
} else if (!tryReadFirstFrame(std::move(packet))) { } else if (!tryReadFirstFrame(std::move(packet))) {
_error(); _error();
} }
} }
bool AudioTrack::initialized() const {
return !_ready;
}
bool AudioTrack::tryReadFirstFrame(Packet &&packet) { bool AudioTrack::tryReadFirstFrame(Packet &&packet) {
// #TODO streaming fix seek to the end. // #TODO streaming fix seek to the end.
if (ProcessPacket(_stream, std::move(packet)).failed()) { if (ProcessPacket(_stream, std::move(packet)).failed()) {
@ -73,17 +80,16 @@ bool AudioTrack::fillStateFromFrame() {
} }
void AudioTrack::mixerInit() { void AudioTrack::mixerInit() {
Expects(!_audioMsgId.playId()); Expects(!initialized());
_audioMsgId = AudioMsgId::ForVideo();
auto data = std::make_unique<VideoSoundData>(); auto data = std::make_unique<VideoSoundData>();
data->frame = _stream.frame.release();
data->context = _stream.codec.release(); data->context = _stream.codec.release();
data->frequency = _stream.frequency; data->frequency = _stream.frequency;
data->length = (_stream.duration * data->frequency) / 1000LL; data->length = (_stream.duration * data->frequency) / 1000LL;
data->speed = _options.speed; data->speed = _options.speed;
Media::Player::mixer()->play( Media::Player::mixer()->play(
_audioMsgId, _audioId,
std::move(data), std::move(data),
_startedPosition); _startedPosition);
} }
@ -103,17 +109,16 @@ void AudioTrack::callReady() {
void AudioTrack::mixerEnqueue(Packet &&packet) { void AudioTrack::mixerEnqueue(Packet &&packet) {
Media::Player::mixer()->feedFromVideo({ Media::Player::mixer()->feedFromVideo({
&packet.fields(), &packet.fields(),
_audioMsgId _audioId
}); });
packet.release(); packet.release();
} }
void AudioTrack::start(crl::time startTime) { void AudioTrack::start(crl::time startTime) {
Expects(_ready == nullptr); Expects(initialized());
Expects(_audioMsgId.playId() != 0);
// #TODO streaming support start() when paused. // #TODO streaming support start() when paused.
Media::Player::mixer()->resume(_audioMsgId, true); Media::Player::mixer()->resume(_audioId, true);
} }
rpl::producer<crl::time> AudioTrack::playPosition() { rpl::producer<crl::time> AudioTrack::playPosition() {
@ -123,12 +128,12 @@ rpl::producer<crl::time> AudioTrack::playPosition() {
_subscription = Media::Player::Updated( _subscription = Media::Player::Updated(
).add_subscription([=](const AudioMsgId &id) { ).add_subscription([=](const AudioMsgId &id) {
using State = Media::Player::State; using State = Media::Player::State;
if (id != _audioMsgId) { if (id != _audioId) {
return; return;
} }
const auto type = AudioMsgId::Type::Video; const auto type = AudioMsgId::Type::Video;
const auto state = Media::Player::mixer()->currentState(type); const auto state = Media::Player::mixer()->currentState(type);
if (state.id != _audioMsgId) { if (state.id != _audioId) {
// #TODO streaming muted by other // #TODO streaming muted by other
return; return;
} else switch (state.state) { } else switch (state.state) {
@ -157,8 +162,8 @@ rpl::producer<crl::time> AudioTrack::playPosition() {
} }
AudioTrack::~AudioTrack() { AudioTrack::~AudioTrack() {
if (_audioMsgId.playId()) { if (_audioId.playId()) {
Media::Player::mixer()->stop(_audioMsgId); Media::Player::mixer()->stop(_audioId);
} }
} }

View file

@ -20,6 +20,7 @@ public:
AudioTrack( AudioTrack(
const PlaybackOptions &options, const PlaybackOptions &options,
Stream &&stream, Stream &&stream,
AudioMsgId audioId,
FnMut<void(const Information &)> ready, FnMut<void(const Information &)> ready,
Fn<void()> error); Fn<void()> error);
@ -44,6 +45,7 @@ public:
private: private:
// Called from the same unspecified thread. // Called from the same unspecified thread.
[[nodiscard]] bool initialized() const;
[[nodiscard]] bool tryReadFirstFrame(Packet &&packet); [[nodiscard]] bool tryReadFirstFrame(Packet &&packet);
[[nodiscard]] bool fillStateFromFrame(); [[nodiscard]] bool fillStateFromFrame();
void mixerInit(); void mixerInit();
@ -54,6 +56,7 @@ private:
// Accessed from the same unspecified thread. // Accessed from the same unspecified thread.
Stream _stream; Stream _stream;
const AudioMsgId _audioId;
bool _noMoreData = false; bool _noMoreData = false;
// Assumed to be thread-safe. // Assumed to be thread-safe.
@ -62,7 +65,6 @@ private:
// First set from the same unspecified thread before _ready is called. // First set from the same unspecified thread before _ready is called.
// After that is immutable. // After that is immutable.
AudioMsgId _audioMsgId;
crl::time _startedPosition = kTimeUnknown; crl::time _startedPosition = kTimeUnknown;
// Accessed from the main thread. // Accessed from the main thread.

View file

@ -8,10 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
namespace Media { namespace Media {
namespace Streaming {
constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min(); constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min();
namespace Streaming {
class VideoTrack; class VideoTrack;
class AudioTrack; class AudioTrack;

View file

@ -158,6 +158,7 @@ void Player::trackPlayedTill(
void Player::audioReceivedTill(crl::time position) { void Player::audioReceivedTill(crl::time position) {
Expects(_audio != nullptr); Expects(_audio != nullptr);
//LOG(("AUDIO TILL: %1").arg(position));
trackReceivedTill(*_audio, _information.audio.state, position); trackReceivedTill(*_audio, _information.audio.state, position);
} }
@ -170,6 +171,7 @@ void Player::audioPlayedTill(crl::time position) {
void Player::videoReceivedTill(crl::time position) { void Player::videoReceivedTill(crl::time position) {
Expects(_video != nullptr); Expects(_video != nullptr);
//LOG(("VIDEO TILL: %1").arg(position));
trackReceivedTill(*_video, _information.video.state, position); trackReceivedTill(*_video, _information.video.state, position);
} }
@ -196,16 +198,21 @@ void Player::fileReady(Stream &&video, Stream &&audio) {
}; };
const auto mode = _options.mode; const auto mode = _options.mode;
if (audio.codec && (mode == Mode::Audio || mode == Mode::Both)) { if (audio.codec && (mode == Mode::Audio || mode == Mode::Both)) {
_audioId = AudioMsgId::ForVideo();
_audio = std::make_unique<AudioTrack>( _audio = std::make_unique<AudioTrack>(
_options, _options,
std::move(audio), std::move(audio),
_audioId,
ready, ready,
error(_audio)); error(_audio));
} else {
_audioId = AudioMsgId();
} }
if (video.codec && (mode == Mode::Video || mode == Mode::Both)) { if (video.codec && (mode == Mode::Video || mode == Mode::Both)) {
_video = std::make_unique<VideoTrack>( _video = std::make_unique<VideoTrack>(
_options, _options,
std::move(video), std::move(video),
_audioId,
ready, ready,
error(_video)); error(_video));
} }

View file

@ -98,9 +98,12 @@ private:
static constexpr auto kReceivedTillEnd static constexpr auto kReceivedTillEnd
= std::numeric_limits<crl::time>::max(); = std::numeric_limits<crl::time>::max();
// Immutable while File is active. // Immutable while File is active after it is ready.
AudioMsgId _audioId;
std::unique_ptr<AudioTrack> _audio; std::unique_ptr<AudioTrack> _audio;
std::unique_ptr<VideoTrack> _video; std::unique_ptr<VideoTrack> _video;
// Immutable while File is active.
base::has_weak_ptr _sessionGuard; base::has_weak_ptr _sessionGuard;
PlaybackOptions _options; PlaybackOptions _options;

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "media/streaming/media_streaming_video_track.h" #include "media/streaming/media_streaming_video_track.h"
#include "media/audio/media_audio.h"
#include "base/concurrent_timer.h" #include "base/concurrent_timer.h"
namespace Media { namespace Media {
@ -28,6 +29,7 @@ public:
const PlaybackOptions &options, const PlaybackOptions &options,
not_null<Shared*> shared, not_null<Shared*> shared,
Stream &&stream, Stream &&stream,
const AudioMsgId &audioId,
FnMut<void(const Information &)> ready, FnMut<void(const Information &)> ready,
Fn<void()> error); Fn<void()> error);
@ -52,7 +54,11 @@ private:
// Force frame position to be clamped to [0, duration] and monotonic. // Force frame position to be clamped to [0, duration] and monotonic.
[[nodiscard]] crl::time currentFramePosition() const; [[nodiscard]] crl::time currentFramePosition() const;
[[nodiscard]] crl::time trackTime() const; struct TrackTime {
crl::time worldNow = kTimeUnknown;
crl::time trackNow = kTimeUnknown;
};
[[nodiscard]] TrackTime trackTime() const;
const crl::weak_on_queue<VideoTrackObject> _weak; const crl::weak_on_queue<VideoTrackObject> _weak;
const PlaybackOptions _options; const PlaybackOptions _options;
@ -62,13 +68,14 @@ private:
Shared *_shared = nullptr; Shared *_shared = nullptr;
Stream _stream; Stream _stream;
AudioMsgId _audioId;
bool _noMoreData = false; bool _noMoreData = false;
FnMut<void(const Information &)> _ready; FnMut<void(const Information &)> _ready;
Fn<void()> _error; Fn<void()> _error;
crl::time _startedTime = kTimeUnknown; crl::time _startedTime = kTimeUnknown;
crl::time _startedPosition = kTimeUnknown; crl::time _startedPosition = kTimeUnknown;
mutable crl::time _previousFramePosition = kTimeUnknown; mutable crl::time _previousFramePosition = kTimeUnknown;
rpl::variable<crl::time> _nextFrameDisplayPosition = kTimeUnknown; rpl::variable<crl::time> _nextFrameDisplayTime = kTimeUnknown;
bool _queued = false; bool _queued = false;
base::ConcurrentTimer _readFramesTimer; base::ConcurrentTimer _readFramesTimer;
@ -80,12 +87,14 @@ VideoTrackObject::VideoTrackObject(
const PlaybackOptions &options, const PlaybackOptions &options,
not_null<Shared*> shared, not_null<Shared*> shared,
Stream &&stream, Stream &&stream,
const AudioMsgId &audioId,
FnMut<void(const Information &)> ready, FnMut<void(const Information &)> ready,
Fn<void()> error) Fn<void()> error)
: _weak(std::move(weak)) : _weak(std::move(weak))
, _options(options) , _options(options)
, _shared(shared) , _shared(shared)
, _stream(std::move(stream)) , _stream(std::move(stream))
, _audioId(audioId)
, _ready(std::move(ready)) , _ready(std::move(ready))
, _error(std::move(error)) , _error(std::move(error))
, _readFramesTimer(_weak, [=] { readFrames(); }) { , _readFramesTimer(_weak, [=] { readFrames(); }) {
@ -94,12 +103,7 @@ VideoTrackObject::VideoTrackObject(
} }
rpl::producer<crl::time> VideoTrackObject::displayFrameAt() const { rpl::producer<crl::time> VideoTrackObject::displayFrameAt() const {
return _nextFrameDisplayPosition.value( return _nextFrameDisplayTime.value();
) | rpl::map([=](crl::time displayPosition) {
return _startedTime
+ crl::time(std::round((displayPosition - _startedPosition)
/ _options.speed));
});
} }
void VideoTrackObject::process(Packet &&packet) { void VideoTrackObject::process(Packet &&packet) {
@ -130,7 +134,7 @@ void VideoTrackObject::readFrames() {
if (interrupted()) { if (interrupted()) {
return; return;
} }
const auto state = _shared->prepareState(trackTime()); const auto state = _shared->prepareState(trackTime().trackNow);
state.match([&](Shared::PrepareFrame frame) { state.match([&](Shared::PrepareFrame frame) {
if (readFrame(frame)) { if (readFrame(frame)) {
presentFrameIfNeeded(); presentFrameIfNeeded();
@ -175,9 +179,12 @@ bool VideoTrackObject::readFrame(not_null<Frame*> frame) {
} }
void VideoTrackObject::presentFrameIfNeeded() { void VideoTrackObject::presentFrameIfNeeded() {
const auto presented = _shared->presentFrame(trackTime()); const auto time = trackTime();
const auto presented = _shared->presentFrame(time.trackNow);
if (presented.displayPosition != kTimeUnknown) { if (presented.displayPosition != kTimeUnknown) {
_nextFrameDisplayPosition = presented.displayPosition; const auto trackLeft = presented.displayPosition - time.trackNow;
_nextFrameDisplayTime = time.worldNow
+ crl::time(std::round(trackLeft / _options.speed));
} }
queueReadFrames(presented.nextCheckDelay); queueReadFrames(presented.nextCheckDelay);
} }
@ -236,7 +243,7 @@ crl::time VideoTrackObject::currentFramePosition() const {
bool VideoTrackObject::fillStateFromFrame() { bool VideoTrackObject::fillStateFromFrame() {
_startedPosition = currentFramePosition(); _startedPosition = currentFramePosition();
_nextFrameDisplayPosition = _startedPosition; _nextFrameDisplayTime = _startedTime;
return (_startedPosition != kTimeUnknown); return (_startedPosition != kTimeUnknown);
} }
@ -261,11 +268,30 @@ void VideoTrackObject::callReady() {
base::take(_ready)({ data }); base::take(_ready)({ data });
} }
crl::time VideoTrackObject::trackTime() const { VideoTrackObject::TrackTime VideoTrackObject::trackTime() const {
return _startedPosition auto result = TrackTime();
+ crl::time((_startedTime != kTimeUnknown const auto started = (_startedTime != kTimeUnknown);
? std::round((crl::now() - _startedTime) * _options.speed) if (!started) {
: 0.)); result.worldNow = crl::now();
result.trackNow = _startedPosition;
return result;
}
const auto correction = _audioId.playId()
? Media::Player::mixer()->getVideoTimeCorrection(_audioId)
: Media::Player::Mixer::TimeCorrection();
const auto knownValue = correction ? correction.audioPositionValue : 0;
const auto knownTime = correction
? correction.audioPositionTime
: _startedTime;
const auto worldNow = crl::now();
const auto sinceKnown = (worldNow - knownTime);
result.worldNow = worldNow;
result.trackNow = _startedPosition
+ knownValue
+ crl::time(std::round(sinceKnown * _options.speed));
return result;
} }
void VideoTrackObject::interrupt() { void VideoTrackObject::interrupt() {
@ -419,6 +445,7 @@ not_null<VideoTrack::Frame*> VideoTrack::Shared::frameForPaint() {
VideoTrack::VideoTrack( VideoTrack::VideoTrack(
const PlaybackOptions &options, const PlaybackOptions &options,
Stream &&stream, Stream &&stream,
const AudioMsgId &audioId,
FnMut<void(const Information &)> ready, FnMut<void(const Information &)> ready,
Fn<void()> error) Fn<void()> error)
: _streamIndex(stream.index) : _streamIndex(stream.index)
@ -429,6 +456,7 @@ VideoTrack::VideoTrack(
options, options,
_shared.get(), _shared.get(),
std::move(stream), std::move(stream),
audioId,
std::move(ready), std::move(ready),
std::move(error)) { std::move(error)) {
} }

View file

@ -23,6 +23,7 @@ public:
VideoTrack( VideoTrack(
const PlaybackOptions &options, const PlaybackOptions &options,
Stream &&stream, Stream &&stream,
const AudioMsgId &audioId,
FnMut<void(const Information &)> ready, FnMut<void(const Information &)> ready,
Fn<void()> error); Fn<void()> error);