Skip to content

Commit 2a22956

Browse files
committed
Apply review suggestions
1 parent 99f01a0 commit 2a22956

5 files changed

Lines changed: 40 additions & 43 deletions

File tree

‎apps/speech/screens/SpeechToTextScreen.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import {
1818
WHISPER_BASE_EN,
1919
WHISPER_BASE_EN_COREML,
2020
WHISPER_SMALL_EN,
21+
WHISPER_SMALL_EN_COREML,
2122
TranscriptionResult,
2223
SpeechToTextProps,
23-
WHISPER_SMALL_EN_COREML,
2424
} from 'react-native-executorch';
2525
import { ModelPicker, ModelOption } from '../components/ModelPicker';
2626

‎packages/react-native-executorch/common/rnexecutorch/models/speech_to_text/common/types/Word.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace rnexecutorch::models::speech_to_text {
66

77
/**
8-
* Basically a different representation of token,
8+
* Different representation of a token,
99
* with timestamps calculated.
1010
*/
1111
struct Word {

‎packages/react-native-executorch/common/rnexecutorch/models/speech_to_text/whisper/ASR.cpp‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -265,15 +265,11 @@ ASR::generate(std::span<const float> waveform, const DecodingOptions &options,
265265
uint64_t startPos = 0;
266266

267267
// Prefill: feed each initial token individually so decode() always sees 1
268-
// token
269-
std::span<uint64_t> firstToken(sequenceIds.data(), 1);
270-
executorch::aten::Tensor logitsTensor =
271-
this->decode(firstToken, encoderFeatures, startPos);
272-
++startPos;
273-
for (size_t i = 1; i < sequenceIds.size(); ++i) {
268+
// token.
269+
executorch::aten::Tensor logitsTensor{nullptr};
270+
for (size_t i = 0; i < sequenceIds.size(); i++, startPos++) {
274271
std::span<uint64_t> single(sequenceIds.data() + i, 1);
275272
logitsTensor = this->decode(single, encoderFeatures, startPos);
276-
++startPos;
277273
}
278274

279275
// Autoregressive decoding: always 1 token at a time

‎packages/react-native-executorch/common/rnexecutorch/models/speech_to_text/whisper/OnlineASR.cpp‎

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <algorithm>
44
#include <iterator>
5+
#include <ranges>
56
#include <utility>
67

78
#include "Constants.h"
@@ -11,18 +12,17 @@
1112
namespace rnexecutorch::models::speech_to_text::whisper::stream {
1213

1314
OnlineASR::OnlineASR(const ASR *asr) : asr_(asr) {
14-
// Reserve an expected amount of memory for audio buffer.
1515
audioBuffer_.reserve((constants::kChunkSize + 1) * constants::kSamplingRate);
1616
}
1717

1818
bool OnlineASR::isReady() const {
19-
std::scoped_lock<std::mutex> lock(streamingMutex);
19+
std::scoped_lock lock(streamingMutex);
2020

2121
return audioBuffer_.size() >= constants::kMinChunkSamples;
2222
}
2323

2424
void OnlineASR::insertAudioChunk(std::span<const float> audio) {
25-
std::scoped_lock<std::mutex> lock(streamingMutex);
25+
std::scoped_lock lock(streamingMutex);
2626

2727
audioBuffer_.insert(audioBuffer_.end(), audio.begin(), audio.end());
2828

@@ -41,12 +41,15 @@ void OnlineASR::insertAudioChunk(std::span<const float> audio) {
4141
}
4242

4343
ProcessResult OnlineASR::process(const DecodingOptions &options) {
44+
constexpr float kStreamSafeBufferMaxSamples =
45+
params::kStreamSafeBufferDuration * constants::kSamplingRate;
46+
4447
std::vector<float> audioCopy;
4548

4649
// Copy the audio buffer to avoid keeping the lock during the entire
4750
// transcription process.
4851
{
49-
std::scoped_lock<std::mutex> lock(streamingMutex);
52+
std::scoped_lock lock(streamingMutex);
5053
audioCopy = audioBuffer_;
5154
}
5255

@@ -60,25 +63,23 @@ ProcessResult OnlineASR::process(const DecodingOptions &options) {
6063
std::vector<Segment> transcriptions = asr_->transcribe(input, options);
6164

6265
// Flatten segments into a single word sequence.
63-
// This is basically our 'nonCommitted' part for now.
66+
// This is our 'nonCommitted' part for now.
6467
std::vector<Word> words;
6568
for (auto &segment : transcriptions) {
66-
std::move(segment.words.begin(), segment.words.end(),
67-
std::back_inserter(words));
69+
std::ranges::move(segment.words, std::back_inserter(words));
6870
}
6971

7072
// Aquire lock for the rest of the method (extensive usage of audioBuffer_).
71-
std::scoped_lock<std::mutex> lock(streamingMutex);
73+
std::scoped_lock lock(streamingMutex);
7274

7375
// Step 1: examine all previously saved EOS points.
7476
// The idea is to remove entries which have changed or no longer exist
7577
// due to model correcting it's output.
76-
for (size_t i = 0; i < memory_.eos.size(); i++) {
77-
const auto &eos = memory_.eos[i];
78-
if (eos.position >= words.size() || !utils::isEos(words[eos.position]) ||
79-
(eos.position > 0 &&
80-
eos.preceeding != words[eos.position - 1].content)) {
81-
memory_.eos.erase(memory_.eos.begin() + i, memory_.eos.end());
78+
for (auto it = memory_.eos.begin(); it != memory_.eos.end(); it++) {
79+
if (it->position >= words.size() || !utils::isEos(words[it->position]) ||
80+
(it->position > 0 &&
81+
it->preceeding != words[it->position - 1].content)) {
82+
memory_.eos.erase(it, memory_.eos.end());
8283
break;
8384
}
8485
}
@@ -92,7 +93,6 @@ ProcessResult OnlineASR::process(const DecodingOptions &options) {
9293
// Because of step 1, we know that if the last EOS exist in eos_,
9394
// then it must be the last entry.
9495
if (memory_.eos.empty() || memory_.eos.back().position != lastEosIndex) {
95-
// Register last EOS entry
9696
std::string preceeding =
9797
lastEosIndex > 0 ? words[lastEosIndex - 1].content : "";
9898
memory_.eos.emplace_back(lastEosIndex, preceeding, lastEosIt->end);
@@ -115,8 +115,8 @@ ProcessResult OnlineASR::process(const DecodingOptions &options) {
115115
// in a 'good' spot - where it will remove a significant audio chunk, yet
116116
// won't affect most recent, unfinished speech samples.
117117
size_t bufferSize = audioBuffer_.size();
118-
if (bufferSize > static_cast<size_t>(params::kStreamSafeBufferDuration *
119-
constants::kSamplingRate)) {
118+
if (std::cmp_greater<size_t, size_t>(bufferSize,
119+
kStreamSafeBufferMaxSamples)) {
120120
auto newCommitted = commitAndClean(words);
121121

122122
committed.insert(committed.end(),
@@ -139,7 +139,7 @@ std::vector<Word> OnlineASR::finish(const DecodingOptions &options) {
139139

140140
// Last-tick committed delta + whatever never made it past the commit
141141
// threshold.
142-
std::vector<Word> residual = std::move(result.committed);
142+
std::vector<Word> residual{std::move(result.committed)};
143143
residual.insert(residual.end(),
144144
std::make_move_iterator(result.nonCommitted.begin()),
145145
std::make_move_iterator(result.nonCommitted.end()));
@@ -150,7 +150,7 @@ std::vector<Word> OnlineASR::finish(const DecodingOptions &options) {
150150
}
151151

152152
void OnlineASR::reset() {
153-
std::scoped_lock<std::mutex> lock(streamingMutex);
153+
std::scoped_lock lock(streamingMutex);
154154

155155
audioBuffer_.clear();
156156

@@ -161,8 +161,16 @@ void OnlineASR::reset() {
161161
}
162162

163163
std::vector<Word> OnlineASR::commitAndClean(std::vector<Word> &transcript) {
164+
constexpr float kMidpointAnchorTime = params::kStreamMaxDuration / 2.0F;
165+
constexpr size_t kMidpointAnchorSamples =
166+
static_cast<size_t>(kMidpointAnchorTime * constants::kSamplingRate);
167+
constexpr size_t kSafetyMarginSamples = static_cast<size_t>(
168+
params::kStreamSafetyThreshold * constants::kSamplingRate);
169+
constexpr float kMaxSafeEosTime =
170+
params::kStreamSafeBufferDuration - params::kStreamSafetyThreshold;
171+
constexpr float kMinDurationToCalculateDensity = 0.1F;
172+
164173
const size_t bufferSize = audioBuffer_.size();
165-
const float midBufferThreshold = params::kStreamMaxDuration / 2.0F;
166174

167175
std::vector<Word> committed;
168176

@@ -184,35 +192,30 @@ std::vector<Word> OnlineASR::commitAndClean(std::vector<Word> &transcript) {
184192
const float eosTimestamp = memory_.eos[0].tmstpend;
185193

186194
const float upperHalfDuration =
187-
std::max(0.0F, eosTimestamp - midBufferThreshold);
195+
std::max(0.0F, eosTimestamp - kMidpointAnchorTime);
188196
const float wordsPerSecond =
189-
upperHalfDuration > 0.1F
197+
upperHalfDuration > kMinDurationToCalculateDensity
190198
? static_cast<float>(transcript.size()) / upperHalfDuration
191199
: 0.0F;
192200

193201
// The EOS sits early enough that cutting up to the safety margin won't
194202
// touch the ongoing (post-EOS) speech.
195-
const bool eosSafe = eosTimestamp < params::kStreamSafeBufferDuration -
196-
params::kStreamSafetyThreshold;
203+
const bool eosSafe = eosTimestamp < kMaxSafeEosTime;
197204

198205
if (!eosSafe && wordsPerSecond < params::kWordsPerSecondLow) {
199206
// EOS lies past the midpoint, but a low word density implies the spoken
200207
// audio is concentrated in the upper half. Drop the lower half and
201208
// shift the EOS accordingly.
202209
audioBuffer_.erase(audioBuffer_.begin(),
203-
audioBuffer_.begin() +
204-
static_cast<size_t>(midBufferThreshold *
205-
constants::kSamplingRate));
206-
memory_.eos[0].tmstpend -= midBufferThreshold;
210+
audioBuffer_.begin() + kMidpointAnchorSamples);
211+
memory_.eos[0].tmstpend -= kMidpointAnchorTime;
207212
} else {
208213
// Cut everything up to and including the sentence — either by the
209214
// safety margin (when EOS is early) or (more aggresively) right at the
210215
// EOS boundary — and commit its words.
211216
const size_t cut =
212217
eosSafe
213-
? bufferSize -
214-
static_cast<size_t>(params::kStreamSafetyThreshold *
215-
constants::kSamplingRate)
218+
? bufferSize - kSafetyMarginSamples
216219
: static_cast<size_t>(eosTimestamp * constants::kSamplingRate);
217220

218221
audioBuffer_.erase(audioBuffer_.begin(), audioBuffer_.begin() + cut);
@@ -237,8 +240,6 @@ std::vector<Word> OnlineASR::commitAndClean(std::vector<Word> &transcript) {
237240

238241
audioBuffer_.erase(audioBuffer_.begin(), audioBuffer_.begin() + cut);
239242

240-
// Move all words up to the last committed position (inclusive) to the
241-
// committed buffer.
242243
committed.insert(
243244
committed.end(), std::make_move_iterator(transcript.begin()),
244245
std::make_move_iterator(transcript.begin() + lastCommittedPos + 1));

‎packages/react-native-executorch/common/rnexecutorch/models/speech_to_text/whisper/Utils.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace rnexecutorch::models::speech_to_text::whisper::utils {
1414
*
1515
* @param word The word to check.
1616
*/
17-
constexpr inline bool isEos(const Word &word) {
17+
inline bool isEos(const Word &word) {
1818
return word.content.size() == 1 &&
1919
constants::kEosPunctations.contains(word.content[0]);
2020
}

0 commit comments

Comments
 (0)