mirror of
https://github.com/telegramdesktop/tdesktop
synced 2024-12-27 00:53:22 +00:00
Support streaming playback speed 0.5 - 2.
This commit is contained in:
parent
26ea6c4e63
commit
ec9512899e
@ -324,9 +324,9 @@ void StartStreaming(
|
||||
base::take(video) = nullptr;
|
||||
});
|
||||
|
||||
player->init(
|
||||
(document->isAudioFile() ? Mode::Audio : Mode::Both),
|
||||
0);
|
||||
auto options = Media::Streaming::PlaybackOptions();
|
||||
options.speed = 1.;
|
||||
player->init(options);
|
||||
player->updates(
|
||||
) | rpl::start_with_next_error_done([=](Update &&update) {
|
||||
update.data.match([&](Information &update) {
|
||||
|
@ -35,11 +35,19 @@ ALCcontext *AudioContext = nullptr;
|
||||
constexpr auto kSuppressRatioAll = 0.2;
|
||||
constexpr auto kSuppressRatioSong = 0.05;
|
||||
constexpr auto kPlaybackSpeedMultiplier = 1.7;
|
||||
constexpr auto kPlaybackSpeedTune = -9;
|
||||
|
||||
auto VolumeMultiplierAll = 1.;
|
||||
auto VolumeMultiplierSong = 1.;
|
||||
|
||||
// Value for AL_PITCH_SHIFTER_COARSE_TUNE effect, 0.5 <= speed <= 2.
|
||||
int CoarseTuneForSpeed(float64 speed) {
|
||||
Expects(speed >= 0.5 && speed <= 2.);
|
||||
|
||||
constexpr auto kTuneSteps = 12;
|
||||
const auto tuneRatio = std::log(speed) / std::log(2.);
|
||||
return -int(std::round(kTuneSteps * tuneRatio));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Media {
|
||||
@ -165,7 +173,7 @@ bool CreatePlaybackDevice() {
|
||||
// initialize the pitch shifter effect
|
||||
alEffecti(_playbackSpeedData.uiEffect, AL_EFFECT_TYPE, AL_EFFECT_PITCH_SHIFTER);
|
||||
// 12 semitones = 1 octave
|
||||
alEffecti(_playbackSpeedData.uiEffect, AL_PITCH_SHIFTER_COARSE_TUNE, kPlaybackSpeedTune);
|
||||
alEffecti(_playbackSpeedData.uiEffect, AL_PITCH_SHIFTER_COARSE_TUNE, CoarseTuneForSpeed(kPlaybackSpeedMultiplier));
|
||||
// connect the effect with the effect slot
|
||||
alAuxiliaryEffectSloti(_playbackSpeedData.uiEffectSlot, AL_EFFECTSLOT_EFFECT, _playbackSpeedData.uiEffect);
|
||||
// initialize a filter to disable the direct (dry) path
|
||||
@ -337,6 +345,22 @@ void Mixer::Track::createStream(AudioMsgId::Type type) {
|
||||
alGenBuffers(3, stream.buffers);
|
||||
if (type == AudioMsgId::Type::Voice) {
|
||||
mixer()->updatePlaybackSpeed(this);
|
||||
} else if (speedEffect) {
|
||||
alGenAuxiliaryEffectSlots(1, &speedEffect->effectSlot);
|
||||
alGenEffects(1, &speedEffect->effect);
|
||||
alGenFilters(1, &speedEffect->filter);
|
||||
alEffecti(speedEffect->effect, AL_EFFECT_TYPE, AL_EFFECT_PITCH_SHIFTER);
|
||||
alEffecti(speedEffect->effect, AL_PITCH_SHIFTER_COARSE_TUNE, speedEffect->coarseTune);
|
||||
alAuxiliaryEffectSloti(speedEffect->effectSlot, AL_EFFECTSLOT_EFFECT, speedEffect->effect);
|
||||
alFilteri(speedEffect->filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
|
||||
alFilterf(speedEffect->filter, AL_LOWPASS_GAIN, 0.f);
|
||||
|
||||
alSourcef(stream.source, AL_PITCH, speedEffect->speed);
|
||||
alSource3i(stream.source, AL_AUXILIARY_SEND_FILTER, speedEffect->effectSlot, 0, 0);
|
||||
alSourcei(stream.source, AL_DIRECT_FILTER, speedEffect->filter);
|
||||
} else {
|
||||
alSource3i(stream.source, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, 0);
|
||||
alSourcei(stream.source, AL_DIRECT_FILTER, AL_FILTER_NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,6 +373,18 @@ void Mixer::Track::destroyStream() {
|
||||
for (auto i = 0; i != 3; ++i) {
|
||||
stream.buffers[i] = 0;
|
||||
}
|
||||
destroySpeedEffect();
|
||||
}
|
||||
|
||||
void Mixer::Track::destroySpeedEffect() {
|
||||
if (!speedEffect) {
|
||||
return;
|
||||
} else if (alIsEffect(speedEffect->effect)) {
|
||||
alDeleteEffects(1, &speedEffect->effect);
|
||||
alDeleteAuxiliaryEffectSlots(1, &speedEffect->effectSlot);
|
||||
alDeleteFilters(1, &speedEffect->filter);
|
||||
}
|
||||
speedEffect = nullptr;
|
||||
}
|
||||
|
||||
void Mixer::Track::reattach(AudioMsgId::Type type) {
|
||||
@ -381,6 +417,7 @@ void Mixer::Track::reattach(AudioMsgId::Type type) {
|
||||
void Mixer::Track::detach() {
|
||||
resetStream();
|
||||
destroyStream();
|
||||
destroySpeedEffect();
|
||||
}
|
||||
|
||||
void Mixer::Track::clear() {
|
||||
@ -402,7 +439,7 @@ void Mixer::Track::clear() {
|
||||
bufferSamples[i] = QByteArray();
|
||||
}
|
||||
|
||||
videoData = nullptr;
|
||||
setVideoData(nullptr);
|
||||
lastUpdateWhen = 0;
|
||||
lastUpdateCorrectedMs = 0;
|
||||
}
|
||||
@ -480,6 +517,16 @@ int Mixer::Track::getNotQueuedBufferIndex() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Mixer::Track::setVideoData(std::unique_ptr<VideoSoundData> data) {
|
||||
destroySpeedEffect();
|
||||
if (data && data->speed != 1.) {
|
||||
speedEffect = std::make_unique<SpeedEffect>();
|
||||
speedEffect->speed = data->speed;
|
||||
speedEffect->coarseTune = CoarseTuneForSpeed(data->speed);
|
||||
}
|
||||
videoData = std::move(data);
|
||||
}
|
||||
|
||||
void Mixer::Track::resetStream() {
|
||||
if (isStreamCreated()) {
|
||||
alSourceStop(stream.source);
|
||||
@ -737,7 +784,7 @@ void Mixer::play(
|
||||
current->lastUpdateWhen = 0;
|
||||
current->lastUpdateCorrectedMs = 0;
|
||||
if (videoData) {
|
||||
current->videoData = std::move(videoData);
|
||||
current->setVideoData(std::move(videoData));
|
||||
} else {
|
||||
current->file = audio.audio()->location(true);
|
||||
current->data = audio.audio()->data();
|
||||
|
@ -193,6 +193,8 @@ private:
|
||||
|
||||
int getNotQueuedBufferIndex();
|
||||
|
||||
void setVideoData(std::unique_ptr<VideoSoundData> data);
|
||||
|
||||
~Track();
|
||||
|
||||
TrackState state;
|
||||
@ -217,12 +219,21 @@ private:
|
||||
Stream stream;
|
||||
std::unique_ptr<VideoSoundData> videoData;
|
||||
|
||||
struct SpeedEffect {
|
||||
uint32 effect = 0;
|
||||
uint32 effectSlot = 0;
|
||||
uint32 filter = 0;
|
||||
int coarseTune = 0;
|
||||
float64 speed = 1.;
|
||||
};
|
||||
std::unique_ptr<SpeedEffect> speedEffect;
|
||||
crl::time lastUpdateWhen = 0;
|
||||
crl::time lastUpdateCorrectedMs = 0;
|
||||
|
||||
private:
|
||||
void createStream(AudioMsgId::Type type);
|
||||
void destroyStream();
|
||||
void destroySpeedEffect();
|
||||
void resetStream();
|
||||
|
||||
};
|
||||
|
@ -14,6 +14,7 @@ struct VideoSoundData {
|
||||
AVFrame *frame = nullptr;
|
||||
int32 frequency = Media::Player::kDefaultFrequency;
|
||||
int64 length = 0;
|
||||
float64 speed = 1.; // 0.5 <= speed <= 2.
|
||||
~VideoSoundData();
|
||||
};
|
||||
|
||||
|
@ -16,10 +16,12 @@ namespace Media {
|
||||
namespace Streaming {
|
||||
|
||||
AudioTrack::AudioTrack(
|
||||
const PlaybackOptions &options,
|
||||
Stream &&stream,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error)
|
||||
: _stream(std::move(stream))
|
||||
: _options(options)
|
||||
, _stream(std::move(stream))
|
||||
, _ready(std::move(ready))
|
||||
, _error(std::move(error)) {
|
||||
Expects(_ready != nullptr);
|
||||
@ -79,6 +81,7 @@ void AudioTrack::mixerInit() {
|
||||
data->context = _stream.codec.release();
|
||||
data->frequency = _stream.frequency;
|
||||
data->length = (_stream.duration * data->frequency) / 1000LL;
|
||||
data->speed = _options.speed;
|
||||
Media::Player::mixer()->play(
|
||||
_audioMsgId,
|
||||
std::move(data),
|
||||
@ -105,7 +108,7 @@ void AudioTrack::mixerEnqueue(Packet &&packet) {
|
||||
packet.release();
|
||||
}
|
||||
|
||||
void AudioTrack::start() {
|
||||
void AudioTrack::start(crl::time startTime) {
|
||||
Expects(_ready == nullptr);
|
||||
Expects(_audioMsgId.playId() != 0);
|
||||
|
||||
|
@ -18,13 +18,14 @@ public:
|
||||
// Called from some unspecified thread.
|
||||
// Callbacks are assumed to be thread-safe.
|
||||
AudioTrack(
|
||||
const PlaybackOptions &options,
|
||||
Stream &&stream,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error);
|
||||
|
||||
// Called from the main thread.
|
||||
// Must be called after 'ready' was invoked.
|
||||
void start();
|
||||
void start(crl::time startTime);
|
||||
|
||||
// Called from the main thread.
|
||||
// Non-const, because we subscribe to changes on the first call.
|
||||
@ -49,6 +50,8 @@ private:
|
||||
void mixerEnqueue(Packet &&packet);
|
||||
void callReady();
|
||||
|
||||
const PlaybackOptions _options;
|
||||
|
||||
// Accessed from the same unspecified thread.
|
||||
Stream _stream;
|
||||
bool _noMoreData = false;
|
||||
|
@ -22,6 +22,12 @@ enum class Mode {
|
||||
Inspection,
|
||||
};
|
||||
|
||||
struct PlaybackOptions {
|
||||
Mode mode = Mode::Both;
|
||||
crl::time position = 0;
|
||||
float64 speed = 1.; // Valid values between 0.5 and 2.
|
||||
};
|
||||
|
||||
struct TrackState {
|
||||
crl::time position = kTimeUnknown;
|
||||
crl::time receivedTill = kTimeUnknown;
|
||||
|
@ -92,11 +92,13 @@ void Player::start() {
|
||||
// video finished
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
_startedTime = crl::now();
|
||||
if (_audio) {
|
||||
_audio->start();
|
||||
_audio->start(_startedTime);
|
||||
}
|
||||
if (_video) {
|
||||
_video->start();
|
||||
_video->start(_startedTime);
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,23 +194,26 @@ void Player::fileReady(Stream &&video, Stream &&audio) {
|
||||
});
|
||||
};
|
||||
};
|
||||
if (audio.codec && (_mode == Mode::Audio || _mode == Mode::Both)) {
|
||||
const auto mode = _options.mode;
|
||||
if (audio.codec && (mode == Mode::Audio || mode == Mode::Both)) {
|
||||
_audio = std::make_unique<AudioTrack>(
|
||||
_options,
|
||||
std::move(audio),
|
||||
ready,
|
||||
error(_audio));
|
||||
}
|
||||
if (video.codec && (_mode == Mode::Video || _mode == Mode::Both)) {
|
||||
if (video.codec && (mode == Mode::Video || mode == Mode::Both)) {
|
||||
_video = std::make_unique<VideoTrack>(
|
||||
_options,
|
||||
std::move(video),
|
||||
ready,
|
||||
error(_video));
|
||||
}
|
||||
if ((_mode == Mode::Audio && !_audio)
|
||||
|| (_mode == Mode::Video && !_video)
|
||||
if ((mode == Mode::Audio && !_audio)
|
||||
|| (mode == Mode::Video && !_video)
|
||||
|| (!_audio && !_video)) {
|
||||
LOG(("Streaming Error: Required stream not found for mode %1."
|
||||
).arg(int(_mode)));
|
||||
).arg(int(mode)));
|
||||
fileError();
|
||||
}
|
||||
}
|
||||
@ -277,8 +282,8 @@ void Player::provideStartInformation() {
|
||||
|| (_video && _information.video.state.duration == kTimeUnknown)) {
|
||||
return; // Not ready yet.
|
||||
} else if ((!_audio && !_video)
|
||||
|| (!_audio && _mode == Mode::Audio)
|
||||
|| (!_video && _mode == Mode::Video)) {
|
||||
|| (!_audio && _options.mode == Mode::Audio)
|
||||
|| (!_video && _options.mode == Mode::Video)) {
|
||||
fail();
|
||||
} else {
|
||||
_stage = Stage::Ready;
|
||||
@ -298,12 +303,14 @@ void Player::fail() {
|
||||
stopGuarded();
|
||||
}
|
||||
|
||||
void Player::init(Mode mode, crl::time position) {
|
||||
void Player::init(const PlaybackOptions &options) {
|
||||
Expects(options.speed >= 0.5 && options.speed <= 2.);
|
||||
|
||||
stop();
|
||||
|
||||
_mode = mode;
|
||||
_options = options;
|
||||
_stage = Stage::Initializing;
|
||||
_file->start(delegate(), position);
|
||||
_file->start(delegate(), _options.position);
|
||||
}
|
||||
|
||||
void Player::pause() {
|
||||
|
@ -33,7 +33,7 @@ public:
|
||||
Player(const Player &other) = delete;
|
||||
Player &operator=(const Player &other) = delete;
|
||||
|
||||
void init(Mode mode, crl::time position);
|
||||
void init(const PlaybackOptions &options);
|
||||
void start();
|
||||
void pause();
|
||||
void resume();
|
||||
@ -102,7 +102,7 @@ private:
|
||||
std::unique_ptr<AudioTrack> _audio;
|
||||
std::unique_ptr<VideoTrack> _video;
|
||||
base::has_weak_ptr _sessionGuard;
|
||||
Mode _mode = Mode::Both;
|
||||
PlaybackOptions _options;
|
||||
|
||||
// Belongs to the File thread while File is active.
|
||||
bool _readTillEnd = false;
|
||||
@ -112,6 +112,7 @@ private:
|
||||
Stage _stage = Stage::Uninitialized;
|
||||
bool _paused = false;
|
||||
|
||||
crl::time _startedTime = kTimeUnknown;
|
||||
crl::time _nextFrameTime = kTimeUnknown;
|
||||
base::Timer _renderFrameTimer;
|
||||
rpl::event_stream<Update, Error> _updates;
|
||||
|
@ -25,6 +25,7 @@ public:
|
||||
|
||||
VideoTrackObject(
|
||||
crl::weak_on_queue<VideoTrackObject> weak,
|
||||
const PlaybackOptions &options,
|
||||
not_null<Shared*> shared,
|
||||
Stream &&stream,
|
||||
FnMut<void(const Information &)> ready,
|
||||
@ -34,7 +35,7 @@ public:
|
||||
|
||||
[[nodisacrd]] rpl::producer<crl::time> displayFrameAt() const;
|
||||
|
||||
void start();
|
||||
void start(crl::time startTime);
|
||||
void interrupt();
|
||||
void frameDisplayed();
|
||||
|
||||
@ -54,6 +55,7 @@ private:
|
||||
[[nodiscard]] crl::time trackTime() const;
|
||||
|
||||
const crl::weak_on_queue<VideoTrackObject> _weak;
|
||||
const PlaybackOptions _options;
|
||||
|
||||
// Main thread wrapper destructor will set _shared back to nullptr.
|
||||
// All queued method calls after that should be discarded.
|
||||
@ -75,11 +77,13 @@ private:
|
||||
|
||||
VideoTrackObject::VideoTrackObject(
|
||||
crl::weak_on_queue<VideoTrackObject> weak,
|
||||
const PlaybackOptions &options,
|
||||
not_null<Shared*> shared,
|
||||
Stream &&stream,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error)
|
||||
: _weak(std::move(weak))
|
||||
, _options(options)
|
||||
, _shared(shared)
|
||||
, _stream(std::move(stream))
|
||||
, _ready(std::move(ready))
|
||||
@ -92,7 +96,9 @@ VideoTrackObject::VideoTrackObject(
|
||||
rpl::producer<crl::time> VideoTrackObject::displayFrameAt() const {
|
||||
return _nextFrameDisplayPosition.value(
|
||||
) | rpl::map([=](crl::time displayPosition) {
|
||||
return _startedTime + (displayPosition - _startedPosition);
|
||||
return _startedTime
|
||||
+ crl::time(std::round((displayPosition - _startedPosition)
|
||||
/ _options.speed));
|
||||
});
|
||||
}
|
||||
|
||||
@ -176,8 +182,8 @@ void VideoTrackObject::presentFrameIfNeeded() {
|
||||
queueReadFrames(presented.nextCheckDelay);
|
||||
}
|
||||
|
||||
void VideoTrackObject::start() {
|
||||
_startedTime = crl::now();
|
||||
void VideoTrackObject::start(crl::time startTime) {
|
||||
_startedTime = startTime;
|
||||
queueReadFrames();
|
||||
}
|
||||
|
||||
@ -257,7 +263,9 @@ void VideoTrackObject::callReady() {
|
||||
|
||||
crl::time VideoTrackObject::trackTime() const {
|
||||
return _startedPosition
|
||||
+ (_startedTime != kTimeUnknown ? (crl::now() - _startedTime) : 0);
|
||||
+ crl::time((_startedTime != kTimeUnknown
|
||||
? std::round((crl::now() - _startedTime) * _options.speed)
|
||||
: 0.));
|
||||
}
|
||||
|
||||
void VideoTrackObject::interrupt() {
|
||||
@ -409,6 +417,7 @@ not_null<VideoTrack::Frame*> VideoTrack::Shared::frameForPaint() {
|
||||
}
|
||||
|
||||
VideoTrack::VideoTrack(
|
||||
const PlaybackOptions &options,
|
||||
Stream &&stream,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error)
|
||||
@ -417,6 +426,7 @@ VideoTrack::VideoTrack(
|
||||
//, _streamRotation(stream.rotation)
|
||||
, _shared(std::make_unique<Shared>())
|
||||
, _wrapped(
|
||||
options,
|
||||
_shared.get(),
|
||||
std::move(stream),
|
||||
std::move(ready),
|
||||
@ -439,9 +449,9 @@ void VideoTrack::process(Packet &&packet) {
|
||||
});
|
||||
}
|
||||
|
||||
void VideoTrack::start() {
|
||||
_wrapped.with([](Implementation &unwrapped) {
|
||||
unwrapped.start();
|
||||
void VideoTrack::start(crl::time startTime) {
|
||||
_wrapped.with([=](Implementation &unwrapped) {
|
||||
unwrapped.start(startTime);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ public:
|
||||
// Called from some unspecified thread.
|
||||
// Callbacks are assumed to be thread-safe.
|
||||
VideoTrack(
|
||||
const PlaybackOptions &options,
|
||||
Stream &&stream,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error);
|
||||
@ -33,7 +34,7 @@ public:
|
||||
void process(Packet &&packet);
|
||||
|
||||
// Called from the main thread.
|
||||
void start();
|
||||
void start(crl::time startTime);
|
||||
// Returns the position of the displayed frame.
|
||||
[[nodiscard]] crl::time markFrameDisplayed(crl::time now);
|
||||
[[nodiscard]] QImage frame(const FrameRequest &request) const;
|
||||
|
Loading…
Reference in New Issue
Block a user