mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-06 15:13:57 +02:00
Added initial ability to pause and resume voice recording.
This commit is contained in:
parent
5130c5df80
commit
091c13bc23
6 changed files with 215 additions and 124 deletions
|
@ -1019,6 +1019,7 @@ PRIVATE
|
||||||
media/audio/media_audio.h
|
media/audio/media_audio.h
|
||||||
media/audio/media_audio_capture.cpp
|
media/audio/media_audio_capture.cpp
|
||||||
media/audio/media_audio_capture.h
|
media/audio/media_audio_capture.h
|
||||||
|
media/audio/media_audio_capture_common.h
|
||||||
media/audio/media_audio_ffmpeg_loader.cpp
|
media/audio/media_audio_ffmpeg_loader.cpp
|
||||||
media/audio/media_audio_ffmpeg_loader.h
|
media/audio/media_audio_ffmpeg_loader.h
|
||||||
media/audio/media_audio_loader.cpp
|
media/audio/media_audio_loader.cpp
|
||||||
|
|
|
@ -84,16 +84,14 @@ enum class FilterType {
|
||||||
const int duration = kPrecision
|
const int duration = kPrecision
|
||||||
* (float64(samples) / ::Media::Player::kDefaultFrequency);
|
* (float64(samples) / ::Media::Player::kDefaultFrequency);
|
||||||
const auto durationString = Ui::FormatDurationText(duration / kPrecision);
|
const auto durationString = Ui::FormatDurationText(duration / kPrecision);
|
||||||
const auto decimalPart = duration % kPrecision;
|
const auto decimalPart = QString::number(duration % kPrecision);
|
||||||
return QString("%1%2%3")
|
return durationString + QLocale().decimalPoint() + decimalPart;
|
||||||
.arg(durationString, QLocale().decimalPoint())
|
|
||||||
.arg(decimalPart);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::unique_ptr<VoiceData> ProcessCaptureResult(
|
[[nodiscard]] std::unique_ptr<VoiceData> ProcessCaptureResult(
|
||||||
const ::Media::Capture::Result &data) {
|
const VoiceWaveform &waveform) {
|
||||||
auto voiceData = std::make_unique<VoiceData>();
|
auto voiceData = std::make_unique<VoiceData>();
|
||||||
voiceData->waveform = data.waveform;
|
voiceData->waveform = waveform;
|
||||||
voiceData->wavemax = voiceData->waveform.empty()
|
voiceData->wavemax = voiceData->waveform.empty()
|
||||||
? uchar(0)
|
? uchar(0)
|
||||||
: *ranges::max_element(voiceData->waveform);
|
: *ranges::max_element(voiceData->waveform);
|
||||||
|
@ -427,12 +425,11 @@ public:
|
||||||
not_null<Ui::RpWidget*> parent,
|
not_null<Ui::RpWidget*> parent,
|
||||||
const style::RecordBar &st,
|
const style::RecordBar &st,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
::Media::Capture::Result &&data,
|
::Media::Capture::Result *data,
|
||||||
const style::font &font);
|
const style::font &font);
|
||||||
|
|
||||||
void requestPaintProgress(float64 progress);
|
void requestPaintProgress(float64 progress);
|
||||||
rpl::producer<> stopRequests() const;
|
rpl::producer<> stopRequests() const;
|
||||||
::Media::Capture::Result *data() const;
|
|
||||||
|
|
||||||
void playPause();
|
void playPause();
|
||||||
|
|
||||||
|
@ -456,7 +453,7 @@ private:
|
||||||
const not_null<DocumentData*> _document;
|
const not_null<DocumentData*> _document;
|
||||||
const std::unique_ptr<VoiceData> _voiceData;
|
const std::unique_ptr<VoiceData> _voiceData;
|
||||||
const std::shared_ptr<Data::DocumentMedia> _mediaView;
|
const std::shared_ptr<Data::DocumentMedia> _mediaView;
|
||||||
const std::unique_ptr<::Media::Capture::Result> _data;
|
const not_null<::Media::Capture::Result*> _data;
|
||||||
const base::unique_qptr<Ui::IconButton> _delete;
|
const base::unique_qptr<Ui::IconButton> _delete;
|
||||||
const style::font &_durationFont;
|
const style::font &_durationFont;
|
||||||
const QString _duration;
|
const QString _duration;
|
||||||
|
@ -486,15 +483,15 @@ ListenWrap::ListenWrap(
|
||||||
not_null<Ui::RpWidget*> parent,
|
not_null<Ui::RpWidget*> parent,
|
||||||
const style::RecordBar &st,
|
const style::RecordBar &st,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
::Media::Capture::Result &&data,
|
::Media::Capture::Result *data,
|
||||||
const style::font &font)
|
const style::font &font)
|
||||||
: _parent(parent)
|
: _parent(parent)
|
||||||
, _st(st)
|
, _st(st)
|
||||||
, _session(session)
|
, _session(session)
|
||||||
, _document(DummyDocument(&session->data()))
|
, _document(DummyDocument(&session->data()))
|
||||||
, _voiceData(ProcessCaptureResult(data))
|
, _voiceData(ProcessCaptureResult(data->waveform))
|
||||||
, _mediaView(_document->createMediaView())
|
, _mediaView(_document->createMediaView())
|
||||||
, _data(std::make_unique<::Media::Capture::Result>(std::move(data)))
|
, _data(data)
|
||||||
, _delete(base::make_unique_q<Ui::IconButton>(parent, _st.remove))
|
, _delete(base::make_unique_q<Ui::IconButton>(parent, _st.remove))
|
||||||
, _durationFont(font)
|
, _durationFont(font)
|
||||||
, _duration(Ui::FormatDurationText(
|
, _duration(Ui::FormatDurationText(
|
||||||
|
@ -817,10 +814,6 @@ rpl::producer<> ListenWrap::stopRequests() const {
|
||||||
return _delete->clicks() | rpl::to_empty;
|
return _delete->clicks() | rpl::to_empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
::Media::Capture::Result *ListenWrap::data() const {
|
|
||||||
return _data.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::lifetime &ListenWrap::lifetime() {
|
rpl::lifetime &ListenWrap::lifetime() {
|
||||||
return _lifetime;
|
return _lifetime;
|
||||||
}
|
}
|
||||||
|
@ -1293,12 +1286,14 @@ void VoiceRecordBar::updateTTLGeometry(
|
||||||
const auto from = -_ttlButton->width();
|
const auto from = -_ttlButton->width();
|
||||||
const auto right = anim::interpolate(from, finalRight, progress);
|
const auto right = anim::interpolate(from, finalRight, progress);
|
||||||
_ttlButton->moveToRight(right, _ttlButton->y());
|
_ttlButton->moveToRight(right, _ttlButton->y());
|
||||||
|
#if 0
|
||||||
} else if (type == TTLAnimationType::TopBottom) {
|
} else if (type == TTLAnimationType::TopBottom) {
|
||||||
const auto ttlFrom = anyTop - _ttlButton->height() * 2;
|
const auto ttlFrom = anyTop - _ttlButton->height() * 2;
|
||||||
const auto ttlTo = anyTop - _lock->height();
|
const auto ttlTo = anyTop - _lock->height();
|
||||||
_ttlButton->moveToLeft(
|
_ttlButton->moveToLeft(
|
||||||
_ttlButton->x(),
|
_ttlButton->x(),
|
||||||
anim::interpolate(ttlFrom, ttlTo, 1. - progress));
|
anim::interpolate(ttlFrom, ttlTo, 1. - progress));
|
||||||
|
#endif
|
||||||
} else if (type == TTLAnimationType::RightTopStatic) {
|
} else if (type == TTLAnimationType::RightTopStatic) {
|
||||||
_ttlButton->moveToRight(
|
_ttlButton->moveToRight(
|
||||||
-_ttlButton->width(),
|
-_ttlButton->width(),
|
||||||
|
@ -1408,48 +1403,7 @@ void VoiceRecordBar::init() {
|
||||||
_showLockAnimation.start(std::move(callback), from, to, duration);
|
_showLockAnimation.start(std::move(callback), from, to, duration);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_lock->setClickedCallback([=] {
|
const auto setLevelAsSend = [=] {
|
||||||
if (!_lock->isStopState()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
::Media::Capture::instance()->startedChanges(
|
|
||||||
) | rpl::filter([=](bool capturing) {
|
|
||||||
return !capturing && _listen;
|
|
||||||
}) | rpl::take(1) | rpl::start_with_next([=] {
|
|
||||||
_lockShowing = false;
|
|
||||||
|
|
||||||
const auto to = 1.;
|
|
||||||
const auto &duration = st::historyRecordVoiceShowDuration;
|
|
||||||
auto callback = [=](float64 value) {
|
|
||||||
_listen->requestPaintProgress(value);
|
|
||||||
const auto reverseValue = to - value;
|
|
||||||
_level->requestPaintProgress(reverseValue);
|
|
||||||
update();
|
|
||||||
if (to == value) {
|
|
||||||
_recordingLifetime.destroy();
|
|
||||||
}
|
|
||||||
updateTTLGeometry(TTLAnimationType::TopBottom, 1. - value);
|
|
||||||
};
|
|
||||||
_showListenAnimation.stop();
|
|
||||||
_showListenAnimation.start(std::move(callback), 0., to, duration);
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
stopRecording(StopType::Listen);
|
|
||||||
});
|
|
||||||
|
|
||||||
_lock->locks(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
if (_hasTTLFilter && _hasTTLFilter()) {
|
|
||||||
if (!_ttlButton) {
|
|
||||||
_ttlButton = std::make_unique<TTLButton>(
|
|
||||||
_outerContainer,
|
|
||||||
_st);
|
|
||||||
}
|
|
||||||
_ttlButton->show();
|
|
||||||
}
|
|
||||||
updateTTLGeometry(TTLAnimationType::RightTopStatic, 0);
|
|
||||||
|
|
||||||
_level->setType(VoiceRecordButton::Type::Send);
|
_level->setType(VoiceRecordButton::Type::Send);
|
||||||
|
|
||||||
_level->clicks(
|
_level->clicks(
|
||||||
|
@ -1464,6 +1418,58 @@ void VoiceRecordBar::init() {
|
||||||
) | rpl::start_with_next([=](bool enter) {
|
) | rpl::start_with_next([=](bool enter) {
|
||||||
_inField = enter;
|
_inField = enter;
|
||||||
}, _recordingLifetime);
|
}, _recordingLifetime);
|
||||||
|
};
|
||||||
|
|
||||||
|
_lock->setClickedCallback([=] {
|
||||||
|
if (isListenState()) {
|
||||||
|
startRecording();
|
||||||
|
_listen = nullptr;
|
||||||
|
setLevelAsSend();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_lock->isStopState()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopRecording(StopType::Listen);
|
||||||
|
});
|
||||||
|
|
||||||
|
_paused.value() | rpl::distinct_until_changed(
|
||||||
|
) | rpl::start_with_next([=](bool paused) {
|
||||||
|
if (!paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// _lockShowing = false;
|
||||||
|
|
||||||
|
const auto to = 1.;
|
||||||
|
const auto &duration = st::historyRecordVoiceShowDuration;
|
||||||
|
auto callback = [=](float64 value) {
|
||||||
|
_listen->requestPaintProgress(value);
|
||||||
|
const auto reverseValue = to - value;
|
||||||
|
_level->requestPaintProgress(reverseValue);
|
||||||
|
update();
|
||||||
|
if (to == value) {
|
||||||
|
_recordingLifetime.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_showListenAnimation.stop();
|
||||||
|
_showListenAnimation.start(std::move(callback), 0., to, duration);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
_lock->locks(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
if (_hasTTLFilter && _hasTTLFilter()) {
|
||||||
|
if (!_ttlButton) {
|
||||||
|
_ttlButton = std::make_unique<TTLButton>(
|
||||||
|
_outerContainer,
|
||||||
|
_st);
|
||||||
|
}
|
||||||
|
_ttlButton->show();
|
||||||
|
}
|
||||||
|
updateTTLGeometry(TTLAnimationType::RightTopStatic, 0);
|
||||||
|
|
||||||
|
setLevelAsSend();
|
||||||
|
|
||||||
const auto &duration = st::historyRecordVoiceShowDuration;
|
const auto &duration = st::historyRecordVoiceShowDuration;
|
||||||
const auto from = 0.;
|
const auto from = 0.;
|
||||||
|
@ -1616,7 +1622,12 @@ void VoiceRecordBar::startRecording() {
|
||||||
startRedCircleAnimation();
|
startRedCircleAnimation();
|
||||||
|
|
||||||
_recording = true;
|
_recording = true;
|
||||||
|
if (_paused.current()) {
|
||||||
|
_paused = false;
|
||||||
|
instance()->pause(false, nullptr);
|
||||||
|
} else {
|
||||||
instance()->start();
|
instance()->start();
|
||||||
|
}
|
||||||
instance()->updated(
|
instance()->updated(
|
||||||
) | rpl::start_with_next_error([=](const Update &update) {
|
) | rpl::start_with_next_error([=](const Update &update) {
|
||||||
_recordingTipRequired = (update.samples < kMinSamples);
|
_recordingTipRequired = (update.samples < kMinSamples);
|
||||||
|
@ -1685,7 +1696,7 @@ void VoiceRecordBar::stop(bool send) {
|
||||||
const auto type = send ? StopType::Send : StopType::Cancel;
|
const auto type = send ? StopType::Send : StopType::Cancel;
|
||||||
stopRecording(type, ttlBeforeHide);
|
stopRecording(type, ttlBeforeHide);
|
||||||
};
|
};
|
||||||
_lockShowing = false;
|
// _lockShowing = false;
|
||||||
visibilityAnimate(false, std::move(disappearanceCallback));
|
visibilityAnimate(false, std::move(disappearanceCallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1695,6 +1706,7 @@ void VoiceRecordBar::finish() {
|
||||||
_inField = false;
|
_inField = false;
|
||||||
_redCircleProgress = 0.;
|
_redCircleProgress = 0.;
|
||||||
_recordingSamples = 0;
|
_recordingSamples = 0;
|
||||||
|
_paused = false;
|
||||||
|
|
||||||
_showAnimation.stop();
|
_showAnimation.stop();
|
||||||
_lockToStopAnimation.stop();
|
_lockToStopAnimation.stop();
|
||||||
|
@ -1704,6 +1716,8 @@ void VoiceRecordBar::finish() {
|
||||||
[[maybe_unused]] const auto s = takeTTLState();
|
[[maybe_unused]] const auto s = takeTTLState();
|
||||||
|
|
||||||
_sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 });
|
_sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 });
|
||||||
|
|
||||||
|
_data = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceRecordBar::hideFast() {
|
void VoiceRecordBar::hideFast() {
|
||||||
|
@ -1719,43 +1733,53 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
|
||||||
instance()->stop(crl::guard(this, [=](Result &&data) {
|
instance()->stop(crl::guard(this, [=](Result &&data) {
|
||||||
_cancelRequests.fire({});
|
_cancelRequests.fire({});
|
||||||
}));
|
}));
|
||||||
|
} else if (type == StopType::Listen) {
|
||||||
|
instance()->pause(true, crl::guard(this, [=](Result &&data) {
|
||||||
|
if (data.bytes.isEmpty()) {
|
||||||
|
// Close everything.
|
||||||
|
stop(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_paused = true;
|
||||||
|
_data = std::move(data);
|
||||||
|
|
||||||
|
window()->raise();
|
||||||
|
window()->activateWindow();
|
||||||
|
_listen = std::make_unique<ListenWrap>(
|
||||||
|
this,
|
||||||
|
_st,
|
||||||
|
&_show->session(),
|
||||||
|
&_data,
|
||||||
|
_cancelFont);
|
||||||
|
_listenChanges.fire({});
|
||||||
|
|
||||||
|
// _lockShowing = false;
|
||||||
|
}));
|
||||||
|
} else if (type == StopType::Send) {
|
||||||
instance()->stop(crl::guard(this, [=](Result &&data) {
|
instance()->stop(crl::guard(this, [=](Result &&data) {
|
||||||
if (data.bytes.isEmpty()) {
|
if (data.bytes.isEmpty()) {
|
||||||
// Close everything.
|
// Close everything.
|
||||||
stop(false);
|
stop(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_data = std::move(data);
|
||||||
|
|
||||||
window()->raise();
|
window()->raise();
|
||||||
window()->activateWindow();
|
window()->activateWindow();
|
||||||
const auto duration = Duration(data.samples);
|
|
||||||
if (type == StopType::Send) {
|
|
||||||
const auto options = Api::SendOptions{
|
const auto options = Api::SendOptions{
|
||||||
.ttlSeconds = (ttlBeforeHide
|
.ttlSeconds = (ttlBeforeHide
|
||||||
? std::numeric_limits<int>::max()
|
? std::numeric_limits<int>::max()
|
||||||
: 0),
|
: 0),
|
||||||
};
|
};
|
||||||
_sendVoiceRequests.fire({
|
_sendVoiceRequests.fire({
|
||||||
data.bytes,
|
_data.bytes,
|
||||||
data.waveform,
|
_data.waveform,
|
||||||
duration,
|
Duration(_data.samples),
|
||||||
options,
|
options,
|
||||||
});
|
});
|
||||||
} else if (type == StopType::Listen) {
|
|
||||||
_listen = std::make_unique<ListenWrap>(
|
|
||||||
this,
|
|
||||||
_st,
|
|
||||||
&_show->session(),
|
|
||||||
std::move(data),
|
|
||||||
_cancelFont);
|
|
||||||
_listenChanges.fire({});
|
|
||||||
|
|
||||||
_lockShowing = false;
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void VoiceRecordBar::drawDuration(QPainter &p) {
|
void VoiceRecordBar::drawDuration(QPainter &p) {
|
||||||
const auto duration = FormatVoiceDuration(_recordingSamples);
|
const auto duration = FormatVoiceDuration(_recordingSamples);
|
||||||
|
@ -1811,14 +1835,13 @@ void VoiceRecordBar::drawMessage(QPainter &p, float64 recordActive) {
|
||||||
|
|
||||||
void VoiceRecordBar::requestToSendWithOptions(Api::SendOptions options) {
|
void VoiceRecordBar::requestToSendWithOptions(Api::SendOptions options) {
|
||||||
if (isListenState()) {
|
if (isListenState()) {
|
||||||
const auto data = _listen->data();
|
|
||||||
if (takeTTLState()) {
|
if (takeTTLState()) {
|
||||||
options.ttlSeconds = std::numeric_limits<int>::max();
|
options.ttlSeconds = std::numeric_limits<int>::max();
|
||||||
}
|
}
|
||||||
_sendVoiceRequests.fire({
|
_sendVoiceRequests.fire({
|
||||||
data->bytes,
|
_data.bytes,
|
||||||
data->waveform,
|
_data.waveform,
|
||||||
Duration(data->samples),
|
Duration(_data.samples),
|
||||||
options,
|
options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1837,7 +1860,7 @@ rpl::producer<> VoiceRecordBar::cancelRequests() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VoiceRecordBar::isRecording() const {
|
bool VoiceRecordBar::isRecording() const {
|
||||||
return _recording.current();
|
return _recording.current() && !_paused.current();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VoiceRecordBar::isRecordingLocked() const {
|
bool VoiceRecordBar::isRecordingLocked() const {
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_common.h"
|
#include "api/api_common.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
#include "history/view/controls/compose_controls_common.h"
|
#include "history/view/controls/compose_controls_common.h"
|
||||||
|
#include "media/audio/media_audio_capture_common.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
#include "ui/round_rect.h"
|
#include "ui/round_rect.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
|
@ -162,6 +163,9 @@ private:
|
||||||
std::unique_ptr<Ui::AbstractButton> _ttlButton;
|
std::unique_ptr<Ui::AbstractButton> _ttlButton;
|
||||||
std::unique_ptr<ListenWrap> _listen;
|
std::unique_ptr<ListenWrap> _listen;
|
||||||
|
|
||||||
|
::Media::Capture::Result _data;
|
||||||
|
rpl::variable<bool> _paused;
|
||||||
|
|
||||||
base::Timer _startTimer;
|
base::Timer _startTimer;
|
||||||
|
|
||||||
rpl::event_stream<SendActionUpdate> _sendActionUpdates;
|
rpl::event_stream<SendActionUpdate> _sendActionUpdates;
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "media/audio/media_audio_capture.h"
|
#include "media/audio/media_audio_capture.h"
|
||||||
|
|
||||||
|
#include "media/audio/media_audio_capture_common.h"
|
||||||
#include "media/audio/media_audio_ffmpeg_loader.h"
|
#include "media/audio/media_audio_ffmpeg_loader.h"
|
||||||
#include "ffmpeg/ffmpeg_utility.h"
|
#include "ffmpeg/ffmpeg_utility.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
@ -37,6 +38,45 @@ bool ErrorHappened(ALCdevice *device) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] VoiceWaveform CollectWaveform(
|
||||||
|
const QVector<uchar> &waveformVector) {
|
||||||
|
if (waveformVector.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto waveform = VoiceWaveform();
|
||||||
|
auto count = int64(waveformVector.size());
|
||||||
|
auto sum = int64(0);
|
||||||
|
if (count >= Player::kWaveformSamplesCount) {
|
||||||
|
auto peaks = QVector<uint16>();
|
||||||
|
peaks.reserve(Player::kWaveformSamplesCount);
|
||||||
|
|
||||||
|
auto peak = uint16(0);
|
||||||
|
for (auto i = int32(0); i < count; ++i) {
|
||||||
|
auto sample = uint16(waveformVector.at(i)) * 256;
|
||||||
|
if (peak < sample) {
|
||||||
|
peak = sample;
|
||||||
|
}
|
||||||
|
sum += Player::kWaveformSamplesCount;
|
||||||
|
if (sum >= count) {
|
||||||
|
sum -= count;
|
||||||
|
peaks.push_back(peak);
|
||||||
|
peak = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0LL);
|
||||||
|
peak = qMax(int32(sum * 1.8 / peaks.size()), 2500);
|
||||||
|
|
||||||
|
waveform.resize(peaks.size());
|
||||||
|
for (int32 i = 0, l = peaks.size(); i != l; ++i) {
|
||||||
|
waveform[i] = char(qMin(
|
||||||
|
31U,
|
||||||
|
uint32(qMin(peaks.at(i), peak)) * 31 / peak));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return waveform;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class Instance::Inner final : public QObject {
|
class Instance::Inner final : public QObject {
|
||||||
|
@ -46,6 +86,7 @@ public:
|
||||||
|
|
||||||
void start(Fn<void(Update)> updated, Fn<void()> error);
|
void start(Fn<void(Update)> updated, Fn<void()> error);
|
||||||
void stop(Fn<void(Result&&)> callback = nullptr);
|
void stop(Fn<void(Result&&)> callback = nullptr);
|
||||||
|
void pause(bool value, Fn<void(Result&&)> callback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void process();
|
void process();
|
||||||
|
@ -67,6 +108,8 @@ private:
|
||||||
base::Timer _timer;
|
base::Timer _timer;
|
||||||
QByteArray _captured;
|
QByteArray _captured;
|
||||||
|
|
||||||
|
bool _paused = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Start() {
|
void Start() {
|
||||||
|
@ -118,6 +161,17 @@ void Instance::stop(Fn<void(Result&&)> callback) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Instance::pause(bool value, Fn<void(Result&&)> callback) {
|
||||||
|
Expects(callback != nullptr || !value);
|
||||||
|
InvokeQueued(_inner.get(), [=] {
|
||||||
|
_inner->pause(value, [=](Result &&result) {
|
||||||
|
crl::on_main([=, result = std::move(result)]() mutable {
|
||||||
|
callback(std::move(result));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Instance::check() {
|
void Instance::check() {
|
||||||
_available = false;
|
_available = false;
|
||||||
if (auto device = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)) {
|
if (auto device = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)) {
|
||||||
|
@ -241,6 +295,9 @@ void Instance::Inner::fail() {
|
||||||
void Instance::Inner::start(Fn<void(Update)> updated, Fn<void()> error) {
|
void Instance::Inner::start(Fn<void(Update)> updated, Fn<void()> error) {
|
||||||
_updated = std::move(updated);
|
_updated = std::move(updated);
|
||||||
_error = std::move(error);
|
_error = std::move(error);
|
||||||
|
if (_paused) {
|
||||||
|
_paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Start OpenAL Capture
|
// Start OpenAL Capture
|
||||||
d->device = alcCaptureOpenDevice(nullptr, kCaptureFrequency, AL_FORMAT_MONO16, kCaptureFrequency / 5);
|
d->device = alcCaptureOpenDevice(nullptr, kCaptureFrequency, AL_FORMAT_MONO16, kCaptureFrequency / 5);
|
||||||
|
@ -404,10 +461,23 @@ void Instance::Inner::start(Fn<void(Update)> updated, Fn<void()> error) {
|
||||||
DEBUG_LOG(("Audio Capture: started!"));
|
DEBUG_LOG(("Audio Capture: started!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Instance::Inner::pause(bool value, Fn<void(Result&&)> callback) {
|
||||||
|
_paused = value;
|
||||||
|
if (!_paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback({
|
||||||
|
d->fullSamples ? d->data : QByteArray(),
|
||||||
|
d->fullSamples ? CollectWaveform(d->waveform) : VoiceWaveform(),
|
||||||
|
qint32(d->fullSamples),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Instance::Inner::stop(Fn<void(Result&&)> callback) {
|
void Instance::Inner::stop(Fn<void(Result&&)> callback) {
|
||||||
if (!_timer.isActive()) {
|
if (!_timer.isActive()) {
|
||||||
return; // in stop() already
|
return; // in stop() already
|
||||||
}
|
}
|
||||||
|
_paused = false;
|
||||||
_timer.cancel();
|
_timer.cancel();
|
||||||
|
|
||||||
const auto needResult = (callback != nullptr);
|
const auto needResult = (callback != nullptr);
|
||||||
|
@ -480,33 +550,7 @@ void Instance::Inner::stop(Fn<void(Result&&)> callback) {
|
||||||
VoiceWaveform waveform;
|
VoiceWaveform waveform;
|
||||||
qint32 samples = d->fullSamples;
|
qint32 samples = d->fullSamples;
|
||||||
if (needResult && samples && !d->waveform.isEmpty()) {
|
if (needResult && samples && !d->waveform.isEmpty()) {
|
||||||
int64 count = d->waveform.size(), sum = 0;
|
waveform = CollectWaveform(d->waveform);
|
||||||
if (count >= Player::kWaveformSamplesCount) {
|
|
||||||
QVector<uint16> peaks;
|
|
||||||
peaks.reserve(Player::kWaveformSamplesCount);
|
|
||||||
|
|
||||||
uint16 peak = 0;
|
|
||||||
for (int32 i = 0; i < count; ++i) {
|
|
||||||
uint16 sample = uint16(d->waveform.at(i)) * 256;
|
|
||||||
if (peak < sample) {
|
|
||||||
peak = sample;
|
|
||||||
}
|
|
||||||
sum += Player::kWaveformSamplesCount;
|
|
||||||
if (sum >= count) {
|
|
||||||
sum -= count;
|
|
||||||
peaks.push_back(peak);
|
|
||||||
peak = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0LL);
|
|
||||||
peak = qMax(int32(sum * 1.8 / peaks.size()), 2500);
|
|
||||||
|
|
||||||
waveform.resize(peaks.size());
|
|
||||||
for (int32 i = 0, l = peaks.size(); i != l; ++i) {
|
|
||||||
waveform[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (hadDevice) {
|
if (hadDevice) {
|
||||||
if (d->codecContext) {
|
if (d->codecContext) {
|
||||||
|
@ -568,6 +612,10 @@ void Instance::Inner::stop(Fn<void(Result&&)> callback) {
|
||||||
void Instance::Inner::process() {
|
void Instance::Inner::process() {
|
||||||
Expects(!d->processing);
|
Expects(!d->processing);
|
||||||
|
|
||||||
|
if (_paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
d->processing = true;
|
d->processing = true;
|
||||||
const auto guard = gsl::finally([&] { d->processing = false; });
|
const auto guard = gsl::finally([&] { d->processing = false; });
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,7 @@ struct Update {
|
||||||
ushort level = 0;
|
ushort level = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Result {
|
struct Result;
|
||||||
QByteArray bytes;
|
|
||||||
VoiceWaveform waveform;
|
|
||||||
int samples = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
void Start();
|
void Start();
|
||||||
void Finish();
|
void Finish();
|
||||||
|
@ -51,6 +47,7 @@ public:
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void stop(Fn<void(Result&&)> callback = nullptr);
|
void stop(Fn<void(Result&&)> callback = nullptr);
|
||||||
|
void pause(bool value, Fn<void(Result&&)> callback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class Inner;
|
class Inner;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Media::Capture {
|
||||||
|
|
||||||
|
struct Result {
|
||||||
|
QByteArray bytes;
|
||||||
|
VoiceWaveform waveform;
|
||||||
|
int samples = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Media::Capture
|
Loading…
Add table
Reference in a new issue