fix: mutex fun: ensure a: stretcher for oboe_cb MusicProvider::onAudioReady, b: musicFile for musicFeedThread

This commit is contained in:
2026-06-11 22:15:42 +02:00
parent 6f39cecc25
commit c16bf6ddd1
2 changed files with 140 additions and 109 deletions

View File

@@ -157,6 +157,13 @@ void PlaybackEngine::initRubberBand() {
} }
void PlaybackEngine::closeRubberBand() { void PlaybackEngine::closeRubberBand() {
if (mPlayer) {
LOGI("PlaybackEngine: mPlayer->stopAudio() ...");
mPlayer->stopAudio();
LOGI("PlaybackEngine: mPlayer->stopAudio() done.");
delete mPlayer;
mPlayer = nullptr;
}
if(musicFeed) { if(musicFeed) {
exitMusicFeedThread.store(true); exitMusicFeedThread.store(true);
LOGI("PlaybackEngine: musicFeed->join() ..."); LOGI("PlaybackEngine: musicFeed->join() ...");
@@ -164,7 +171,6 @@ void PlaybackEngine::closeRubberBand() {
LOGI("PlaybackEngine: musicFeed->join() done."); LOGI("PlaybackEngine: musicFeed->join() done.");
musicFeed = nullptr; musicFeed = nullptr;
} }
closeMusicFile();
} }
void PlaybackEngine::closeMusicFile() { void PlaybackEngine::closeMusicFile() {
@@ -209,6 +215,7 @@ void PlaybackEngine::musicFeedThread() {
std::mt19937 mt(42); std::mt19937 mt(42);
std::uniform_real_distribution<double> dist(0.98, 1.0); std::uniform_real_distribution<double> dist(0.98, 1.0);
*/ */
bool isOnTrackFinished = false;
LOGI("starting musicFeedThread()"); LOGI("starting musicFeedThread()");
// strecher num channels: same as output num channels // strecher num channels: same as output num channels
@@ -243,6 +250,11 @@ void PlaybackEngine::musicFeedThread() {
// thread 2: polling for decoding more mp3 -> process() -- getSamplesRequired() // thread 2: polling for decoding more mp3 -> process() -- getSamplesRequired()
while(!exitMusicFeedThread.load()) { while(!exitMusicFeedThread.load()) {
if(isOnTrackFinished) {
if(listener) listener->onTrackFinished();
isOnTrackFinished = false;
}
if(!haveMusicFile.load()) { if(!haveMusicFile.load()) {
// while no MusicProvider is connected, no samples will be read from 'stretcher' // while no MusicProvider is connected, no samples will be read from 'stretcher'
// therefore, we do not write any samples into it! // therefore, we do not write any samples into it!
@@ -325,100 +337,109 @@ void PlaybackEngine::musicFeedThread() {
continue; continue;
} }
if(idebug++ < 10) { size_t num_decoded_samples;
pr = std::max(1L, musicFile->rate); {
loop_delay_us = std::min((size_t) 50000, (size_t) 1000000 * num_samples / pr); std::lock_guard<std::mutex> lock(mMusicLock);
LOGI("feed %d music samples", num_samples); if(!haveMusicFile.load()) continue;
// feed 1024 music samples
// => stretcher is asking for 1024 = getSamplesRequired() if(idebug++ < 10) {
// ca. 21 ms pr = std::max(1L, musicFile->rate);
loop_delay_us = std::min((size_t) 50000, (size_t) 1000000 * num_samples / pr);
LOGI("feed %d music samples", num_samples);
// feed 1024 music samples
// => stretcher is asking for 1024 = getSamplesRequired()
// ca. 21 ms
/* /*
int64_t a = mpg123_seek(musicFile->handle, 0, SEEK_CUR); int64_t a = mpg123_seek(musicFile->handle, 0, SEEK_CUR);
int64_t b = mpg123_tell(musicFile->handle); int64_t b = mpg123_tell(musicFile->handle);
LOGI(" checking mpg123_seek(): len=%lld seek=%lld tell=%lld", LOGI(" checking mpg123_seek(): len=%lld seek=%lld tell=%lld",
(long long)musicFile->num_samples, (long long)a, (long long)b); (long long)musicFile->num_samples, (long long)a, (long long)b);
*/ */
} }
markerPos.store((musicFile->num_samples - musicFile->remaining_samples) / musicFile->samples_per_frame * musicFile->secs_per_frame); markerPos.store((musicFile->num_samples - musicFile->remaining_samples) / musicFile->samples_per_frame * musicFile->secs_per_frame);
duration.store(musicFile->duration); duration.store(musicFile->duration);
if(requestSeek.load()) { if(requestSeek.load()) {
double pos = seekPos.load(); double pos = seekPos.load();
// compute seek target in samples, clip to [0..num_samples] // compute seek target in samples, clip to [0..num_samples]
auto seek_samples = static_cast<long>(pos * musicFile->samples_per_frame / musicFile->secs_per_frame); auto seek_samples = static_cast<long>(pos * musicFile->samples_per_frame / musicFile->secs_per_frame);
LOGI("seek to %.3lf sec = %lld samples", pos, (long long) seek_samples); LOGI("seek to %.3lf sec = %lld samples", pos, (long long) seek_samples);
seek_samples = std::max(seek_samples, 0L); seek_samples = std::max(seek_samples, 0L);
seek_samples = std::min(seek_samples, musicFile->num_samples); seek_samples = std::min(seek_samples, musicFile->num_samples);
// perform the seek // perform the seek
off_t seekResult = mp3file_seek(musicFile.get(), seek_samples); off_t seekResult = mp3file_seek(musicFile.get(), seek_samples);
idebug = 0; idebug = 0;
/*if(seekResult > seek_samples) { /*if(seekResult > seek_samples) {
LOGE("error seeking in mp3 file: mpg123_seek(handle, %ld, SEEK_SET)=%lld", seek_samples, (long long) seekResult); LOGE("error seeking in mp3 file: mpg123_seek(handle, %ld, SEEK_SET)=%lld", seek_samples, (long long) seekResult);
seekPos.store(pos * dist(mt)); // randomize seek target a bit seekPos.store(pos * dist(mt)); // randomize seek target a bit
} else {*/ } else {*/
// update structure // update structure
// compute seek delta, to update byte 'offset' // compute seek delta, to update byte 'offset'
//auto seek_samples_delta = seekResult - (musicFile->num_samples - musicFile->remaining_samples); //auto seek_samples_delta = seekResult - (musicFile->num_samples - musicFile->remaining_samples);
//auto seek_bytes_delta = seek_samples_delta * num_ch_in * sizeof(int16_t); //auto seek_bytes_delta = seek_samples_delta * num_ch_in * sizeof(int16_t);
musicFile->remaining_samples = static_cast<int>(musicFile->num_samples - seekResult); musicFile->remaining_samples = static_cast<int>(musicFile->num_samples - seekResult);
//musicFile->offset += seek_bytes_delta; //musicFile->offset += seek_bytes_delta;
musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR); musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR);
// it is not possible to obtain bytes offset reliably?? // it is not possible to obtain bytes offset reliably??
//musicFile->num_bytes = -1; // pretend we can read until the end //musicFile->num_bytes = -1; // pretend we can read until the end
requestSeek.store(false); requestSeek.store(false);
/*}*/ /*}*/
} }
size_t done = 0; // bytes! size_t done = 0; // bytes!
size_t read_size_bytes = std::min(num_samples * num_ch_in * sizeof(int16_t), cbuf_size_bytes); size_t read_size_bytes = std::min(num_samples * num_ch_in * sizeof(int16_t), cbuf_size_bytes);
/*if (musicFile->offset != 0 && musicFile->num_bytes != -1) { read_size_bytes = std::min(read_size_bytes_calc, (size_t) musicFile->num_bytes - musicFile->offset); }*/ /*if (musicFile->offset != 0 && musicFile->num_bytes != -1) { read_size_bytes = std::min(read_size_bytes_calc, (size_t) musicFile->num_bytes - musicFile->offset); }*/
//if (idebug < 10) LOGI("mft rsbc=%lld rsb=%lld nb=%lld o=%lld", (long long) read_size_bytes_calc, (long long) read_size_bytes, (long long) musicFile->num_bytes, (long long) musicFile->offset); //if (idebug < 10) LOGI("mft rsbc=%lld rsb=%lld nb=%lld o=%lld", (long long) read_size_bytes_calc, (long long) read_size_bytes, (long long) musicFile->num_bytes, (long long) musicFile->offset);
int err = mpg123_read(musicFile->handle, cbuf, read_size_bytes, &done); int err = mpg123_read(musicFile->handle, cbuf, read_size_bytes, &done);
//if (idebug < 10) LOGI("mft done=%lld err=%lld small=%d", (long long) done, (long long) err, read_size_bytes < read_size_bytes_calc); //if (idebug < 10) LOGI("mft done=%lld err=%lld small=%d", (long long) done, (long long) err, read_size_bytes < read_size_bytes_calc);
/*if (musicFile->offset != 0 && musicFile->num_bytes != -1 && err == MPG123_OK && read_size_bytes < read_size_bytes_calc) { /*if (musicFile->offset != 0 && musicFile->num_bytes != -1 && err == MPG123_OK && read_size_bytes < read_size_bytes_calc) {
LOGW("out of bytes, finished playing"); LOGW("out of bytes, finished playing");
err = MPG123_DONE; err = MPG123_DONE;
}*/ }*/
musicFile->remaining_samples -= done / num_ch_in / sizeof(int16_t); musicFile->remaining_samples -= done / num_ch_in / sizeof(int16_t);
//musicFile->offset += done; //musicFile->offset += done;
musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR); musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR);
if(musicFile->offset >= musicFile->num_bytes) { if(musicFile->offset >= musicFile->num_bytes) {
// TODO: this is inaccurate. either we do not decode everything, or we overrun past the end // TODO: this is inaccurate. either we do not decode everything, or we overrun past the end
// TODO: maybe decode as a "stream" instead. // TODO: maybe decode as a "stream" instead.
LOGW("out of bytes, finished playing"); LOGW("out of bytes, finished playing");
err = MPG123_DONE; err = MPG123_DONE;
} }
if (err != MPG123_OK && err != MPG123_DONE) { if (err != MPG123_OK && err != MPG123_DONE) {
// error! // error!
LOGE("error reading mp3 file: mpg123_read() err=%d done=%d", err, done); LOGE("error reading mp3 file: mpg123_read() err=%d done=%d", err, done);
// next iteration will play silence // next iteration will play silence
closeMusicFile(); closeMusicFile();
stretcher.setTimeRatio(1.0); // buffer size for playing silence is computed from 'playbackRate', so reset timeRatio stretcher.setTimeRatio(1.0); // buffer size for playing silence is computed from 'playbackRate', so reset timeRatio
stretcher.setPitchScale(1.0); stretcher.setPitchScale(1.0);
stretcher.process(buf_ptr, 0, true); // set end of playback stretcher.process(buf_ptr, 0, true); // set end of playback
mPlayer->stopAudio(); mPlayer->stopAudio();
continue; // TODO: raise error
} //if(listener) listener->onError(1, "mpg123_read() error");
if(err == MPG123_DONE) { continue;
// next iteration will play silence }
// we keep Stretcher and Oboe alive if(err == MPG123_DONE) {
LOGI("finished reading mp3 file (MPG123_DONE)"); // next iteration will play silence
idebug = 0; // we keep Stretcher and Oboe alive
closeMusicFile(); LOGI("finished reading mp3 file (MPG123_DONE)");
if(listener) listener->onTrackFinished(); idebug = 0;
continue; closeMusicFile();
} //if(listener) listener->onTrackFinished();
isOnTrackFinished = true;
continue;
}
size_t num_decoded_samples = done / sizeof(int16_t) / num_ch_in; num_decoded_samples = done / sizeof(int16_t) / num_ch_in;
//LOGD("num_decoded_samples = %d", num_decoded_samples); //LOGD("num_decoded_samples = %d", num_decoded_samples);
// * convert interleaved int16 to de-interleaved float [-1.0, 1.0] format // * convert interleaved int16 to de-interleaved float [-1.0, 1.0] format
// * map input to output channels // * map input to output channels
for(size_t i = 0; i < num_decoded_samples; i++) { for(size_t i = 0; i < num_decoded_samples; i++) {
for(size_t j = 0; j < num_ch_out; j++) { for(size_t j = 0; j < num_ch_out; j++) {
buf[i + buf_stride * j] = static_cast<float>(*(reinterpret_cast<int16_t*>(cbuf) + i * num_ch_in + channel_map[j])) / 32768.0f; buf[i + buf_stride * j] = static_cast<float>(*(reinterpret_cast<int16_t*>(cbuf) + i * num_ch_in + channel_map[j])) / 32768.0f;
}
} }
} }
@@ -426,6 +447,11 @@ void PlaybackEngine::musicFeedThread() {
stretcher.process(buf_ptr, num_decoded_samples, false); stretcher.process(buf_ptr, num_decoded_samples, false);
} }
if(isOnTrackFinished) {
if(listener) listener->onTrackFinished();
//isOnTrackFinished = false;
}
LOGI("musicFeedThread() exiting ..."); LOGI("musicFeedThread() exiting ...");
free(buf); free(buf);
@@ -462,12 +488,9 @@ void PlaybackEngine::seekTo(double markerPosSec) {
void PlaybackEngine::stop() { void PlaybackEngine::stop() {
LOGI("PlaybackEngine::stop()"); LOGI("PlaybackEngine::stop()");
closeRubberBand(); closeRubberBand();
if (mPlayer) { {
LOGI("PlaybackEngine: mPlayer->stopAudio() ..."); std::lock_guard<std::mutex> lock(mMusicLock);
mPlayer->stopAudio(); closeMusicFile();
LOGI("PlaybackEngine: mPlayer->stopAudio() done.");
delete mPlayer;
mPlayer = nullptr;
} }
markerPos.store(0.0); markerPos.store(0.0);
duration.store(0.0); duration.store(0.0);
@@ -476,10 +499,9 @@ void PlaybackEngine::stop() {
PlaybackEngine::~PlaybackEngine() { PlaybackEngine::~PlaybackEngine() {
LOGI("~PlaybackEngine()"); LOGI("~PlaybackEngine()");
closeRubberBand(); closeRubberBand();
if (mPlayer) { {
mPlayer->stopAudio(); std::lock_guard<std::mutex> lock(mMusicLock);
delete mPlayer; closeMusicFile();
mPlayer = nullptr;
} }
} }
@@ -490,18 +512,25 @@ void PlaybackEngine::playBeat() {
void PlaybackEngine::playMusic(int fd, long long offset, long long length) { void PlaybackEngine::playMusic(int fd, long long offset, long long length) {
if(!mPlayer) return; if(!mPlayer) return;
LOGI("PlaybackEngine::playMusic(fd=%d)", fd); LOGI("PlaybackEngine::playMusic(fd=%d)", fd);
if(musicFile) {
// when changing tracks in the UI, close the previous file {
closeMusicFile(); std::lock_guard<std::mutex> lock(mMusicLock);
}
android_fd = fd; if (musicFile) {
if(fd != 0) if (fd == android_fd)
musicFile.reset(mp3file_open_fd(android_fd, offset, length, 0)); android_fd = 0; // HACK: avoid closing the 'fd' if re-opening the same file
if(musicFile) { // when changing tracks in the UI, close the previous file
timeRatio.store(((double) playbackRate.load()) / ((double) musicFile->rate)); closeMusicFile();
haveTimeRatio.store(true); }
numInChannels.store(musicFile->channels); android_fd = fd;
haveMusicFile.store(true); if (fd != 0)
musicFile.reset(mp3file_open_fd(android_fd, offset, length, 0));
if (musicFile) {
timeRatio.store(((double) playbackRate.load()) / ((double) musicFile->rate));
haveTimeRatio.store(true);
numInChannels.store(musicFile->channels);
haveMusicFile.store(true);
}
} }
bool is_finished = (stretcher.available() == -1); bool is_finished = (stretcher.available() == -1);
@@ -509,6 +538,7 @@ void PlaybackEngine::playMusic(int fd, long long offset, long long length) {
LOGE("stretcher.available() == -1, this should not happen anymore with current wiring"); LOGE("stretcher.available() == -1, this should not happen anymore with current wiring");
// so that we may play again after "final chunk" // so that we may play again after "final chunk"
closeRubberBand(); closeRubberBand();
// do not closeMusicFile() here, it would close the very file we want to be playing
initRubberBand(); initRubberBand();
} }

View File

@@ -62,6 +62,7 @@ private:
RubberBand::RubberBandStretcher stretcher; RubberBand::RubberBandStretcher stretcher;
MixingPlayer *mPlayer; MixingPlayer *mPlayer;
std::string mFilesDir; std::string mFilesDir;
std::mutex mMusicLock;
std::unique_ptr<MP3File> musicFile; std::unique_ptr<MP3File> musicFile;
std::atomic<bool> haveMusicFile; std::atomic<bool> haveMusicFile;
std::unique_ptr<std::thread> musicFeed; std::unique_ptr<std::thread> musicFeed;