fix: mutex fun: ensure a: stretcher for oboe_cb MusicProvider::onAudioReady, b: musicFile for musicFeedThread
This commit is contained in:
@@ -157,6 +157,13 @@ void PlaybackEngine::initRubberBand() {
|
||||
}
|
||||
|
||||
void PlaybackEngine::closeRubberBand() {
|
||||
if (mPlayer) {
|
||||
LOGI("PlaybackEngine: mPlayer->stopAudio() ...");
|
||||
mPlayer->stopAudio();
|
||||
LOGI("PlaybackEngine: mPlayer->stopAudio() done.");
|
||||
delete mPlayer;
|
||||
mPlayer = nullptr;
|
||||
}
|
||||
if(musicFeed) {
|
||||
exitMusicFeedThread.store(true);
|
||||
LOGI("PlaybackEngine: musicFeed->join() ...");
|
||||
@@ -164,7 +171,6 @@ void PlaybackEngine::closeRubberBand() {
|
||||
LOGI("PlaybackEngine: musicFeed->join() done.");
|
||||
musicFeed = nullptr;
|
||||
}
|
||||
closeMusicFile();
|
||||
}
|
||||
|
||||
void PlaybackEngine::closeMusicFile() {
|
||||
@@ -209,6 +215,7 @@ void PlaybackEngine::musicFeedThread() {
|
||||
std::mt19937 mt(42);
|
||||
std::uniform_real_distribution<double> dist(0.98, 1.0);
|
||||
*/
|
||||
bool isOnTrackFinished = false;
|
||||
LOGI("starting musicFeedThread()");
|
||||
|
||||
// strecher num channels: same as output num channels
|
||||
@@ -243,6 +250,11 @@ void PlaybackEngine::musicFeedThread() {
|
||||
// thread 2: polling for decoding more mp3 -> process() -- getSamplesRequired()
|
||||
while(!exitMusicFeedThread.load()) {
|
||||
|
||||
if(isOnTrackFinished) {
|
||||
if(listener) listener->onTrackFinished();
|
||||
isOnTrackFinished = false;
|
||||
}
|
||||
|
||||
if(!haveMusicFile.load()) {
|
||||
// while no MusicProvider is connected, no samples will be read from 'stretcher'
|
||||
// therefore, we do not write any samples into it!
|
||||
@@ -325,100 +337,109 @@ void PlaybackEngine::musicFeedThread() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(idebug++ < 10) {
|
||||
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
|
||||
size_t num_decoded_samples;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMusicLock);
|
||||
if(!haveMusicFile.load()) continue;
|
||||
|
||||
if(idebug++ < 10) {
|
||||
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 b = mpg123_tell(musicFile->handle);
|
||||
LOGI(" checking mpg123_seek(): len=%lld seek=%lld tell=%lld",
|
||||
(long long)musicFile->num_samples, (long long)a, (long long)b);
|
||||
int64_t a = mpg123_seek(musicFile->handle, 0, SEEK_CUR);
|
||||
int64_t b = mpg123_tell(musicFile->handle);
|
||||
LOGI(" checking mpg123_seek(): len=%lld seek=%lld tell=%lld",
|
||||
(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);
|
||||
duration.store(musicFile->duration);
|
||||
markerPos.store((musicFile->num_samples - musicFile->remaining_samples) / musicFile->samples_per_frame * musicFile->secs_per_frame);
|
||||
duration.store(musicFile->duration);
|
||||
|
||||
if(requestSeek.load()) {
|
||||
double pos = seekPos.load();
|
||||
// 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);
|
||||
LOGI("seek to %.3lf sec = %lld samples", pos, (long long) seek_samples);
|
||||
seek_samples = std::max(seek_samples, 0L);
|
||||
seek_samples = std::min(seek_samples, musicFile->num_samples);
|
||||
// perform the seek
|
||||
off_t seekResult = mp3file_seek(musicFile.get(), seek_samples);
|
||||
idebug = 0;
|
||||
/*if(seekResult > seek_samples) {
|
||||
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
|
||||
} else {*/
|
||||
// update structure
|
||||
// compute seek delta, to update byte 'offset'
|
||||
//auto seek_samples_delta = seekResult - (musicFile->num_samples - musicFile->remaining_samples);
|
||||
//auto seek_bytes_delta = seek_samples_delta * num_ch_in * sizeof(int16_t);
|
||||
musicFile->remaining_samples = static_cast<int>(musicFile->num_samples - seekResult);
|
||||
//musicFile->offset += seek_bytes_delta;
|
||||
musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR);
|
||||
// it is not possible to obtain bytes offset reliably??
|
||||
//musicFile->num_bytes = -1; // pretend we can read until the end
|
||||
requestSeek.store(false);
|
||||
/*}*/
|
||||
}
|
||||
if(requestSeek.load()) {
|
||||
double pos = seekPos.load();
|
||||
// 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);
|
||||
LOGI("seek to %.3lf sec = %lld samples", pos, (long long) seek_samples);
|
||||
seek_samples = std::max(seek_samples, 0L);
|
||||
seek_samples = std::min(seek_samples, musicFile->num_samples);
|
||||
// perform the seek
|
||||
off_t seekResult = mp3file_seek(musicFile.get(), seek_samples);
|
||||
idebug = 0;
|
||||
/*if(seekResult > seek_samples) {
|
||||
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
|
||||
} else {*/
|
||||
// update structure
|
||||
// compute seek delta, to update byte 'offset'
|
||||
//auto seek_samples_delta = seekResult - (musicFile->num_samples - musicFile->remaining_samples);
|
||||
//auto seek_bytes_delta = seek_samples_delta * num_ch_in * sizeof(int16_t);
|
||||
musicFile->remaining_samples = static_cast<int>(musicFile->num_samples - seekResult);
|
||||
//musicFile->offset += seek_bytes_delta;
|
||||
musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR);
|
||||
// it is not possible to obtain bytes offset reliably??
|
||||
//musicFile->num_bytes = -1; // pretend we can read until the end
|
||||
requestSeek.store(false);
|
||||
/*}*/
|
||||
}
|
||||
|
||||
size_t done = 0; // 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 (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);
|
||||
//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) {
|
||||
LOGW("out of bytes, finished playing");
|
||||
err = MPG123_DONE;
|
||||
}*/
|
||||
musicFile->remaining_samples -= done / num_ch_in / sizeof(int16_t);
|
||||
//musicFile->offset += done;
|
||||
musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR);
|
||||
if(musicFile->offset >= musicFile->num_bytes) {
|
||||
// TODO: this is inaccurate. either we do not decode everything, or we overrun past the end
|
||||
// TODO: maybe decode as a "stream" instead.
|
||||
LOGW("out of bytes, finished playing");
|
||||
err = MPG123_DONE;
|
||||
}
|
||||
if (err != MPG123_OK && err != MPG123_DONE) {
|
||||
// error!
|
||||
LOGE("error reading mp3 file: mpg123_read() err=%d done=%d", err, done);
|
||||
// next iteration will play silence
|
||||
closeMusicFile();
|
||||
stretcher.setTimeRatio(1.0); // buffer size for playing silence is computed from 'playbackRate', so reset timeRatio
|
||||
stretcher.setPitchScale(1.0);
|
||||
stretcher.process(buf_ptr, 0, true); // set end of playback
|
||||
mPlayer->stopAudio();
|
||||
continue;
|
||||
}
|
||||
if(err == MPG123_DONE) {
|
||||
// next iteration will play silence
|
||||
// we keep Stretcher and Oboe alive
|
||||
LOGI("finished reading mp3 file (MPG123_DONE)");
|
||||
idebug = 0;
|
||||
closeMusicFile();
|
||||
if(listener) listener->onTrackFinished();
|
||||
continue;
|
||||
}
|
||||
size_t done = 0; // 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 (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);
|
||||
//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) {
|
||||
LOGW("out of bytes, finished playing");
|
||||
err = MPG123_DONE;
|
||||
}*/
|
||||
musicFile->remaining_samples -= done / num_ch_in / sizeof(int16_t);
|
||||
//musicFile->offset += done;
|
||||
musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR);
|
||||
if(musicFile->offset >= musicFile->num_bytes) {
|
||||
// TODO: this is inaccurate. either we do not decode everything, or we overrun past the end
|
||||
// TODO: maybe decode as a "stream" instead.
|
||||
LOGW("out of bytes, finished playing");
|
||||
err = MPG123_DONE;
|
||||
}
|
||||
if (err != MPG123_OK && err != MPG123_DONE) {
|
||||
// error!
|
||||
LOGE("error reading mp3 file: mpg123_read() err=%d done=%d", err, done);
|
||||
// next iteration will play silence
|
||||
closeMusicFile();
|
||||
stretcher.setTimeRatio(1.0); // buffer size for playing silence is computed from 'playbackRate', so reset timeRatio
|
||||
stretcher.setPitchScale(1.0);
|
||||
stretcher.process(buf_ptr, 0, true); // set end of playback
|
||||
mPlayer->stopAudio();
|
||||
// TODO: raise error
|
||||
//if(listener) listener->onError(1, "mpg123_read() error");
|
||||
continue;
|
||||
}
|
||||
if(err == MPG123_DONE) {
|
||||
// next iteration will play silence
|
||||
// we keep Stretcher and Oboe alive
|
||||
LOGI("finished reading mp3 file (MPG123_DONE)");
|
||||
idebug = 0;
|
||||
closeMusicFile();
|
||||
//if(listener) listener->onTrackFinished();
|
||||
isOnTrackFinished = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t num_decoded_samples = done / sizeof(int16_t) / num_ch_in;
|
||||
//LOGD("num_decoded_samples = %d", num_decoded_samples);
|
||||
num_decoded_samples = done / sizeof(int16_t) / num_ch_in;
|
||||
//LOGD("num_decoded_samples = %d", num_decoded_samples);
|
||||
|
||||
// * convert interleaved int16 to de-interleaved float [-1.0, 1.0] format
|
||||
// * map input to output channels
|
||||
for(size_t i = 0; i < num_decoded_samples; i++) {
|
||||
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;
|
||||
// * convert interleaved int16 to de-interleaved float [-1.0, 1.0] format
|
||||
// * map input to output channels
|
||||
for(size_t i = 0; i < num_decoded_samples; i++) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,6 +447,11 @@ void PlaybackEngine::musicFeedThread() {
|
||||
stretcher.process(buf_ptr, num_decoded_samples, false);
|
||||
}
|
||||
|
||||
if(isOnTrackFinished) {
|
||||
if(listener) listener->onTrackFinished();
|
||||
//isOnTrackFinished = false;
|
||||
}
|
||||
|
||||
LOGI("musicFeedThread() exiting ...");
|
||||
|
||||
free(buf);
|
||||
@@ -462,12 +488,9 @@ void PlaybackEngine::seekTo(double markerPosSec) {
|
||||
void PlaybackEngine::stop() {
|
||||
LOGI("PlaybackEngine::stop()");
|
||||
closeRubberBand();
|
||||
if (mPlayer) {
|
||||
LOGI("PlaybackEngine: mPlayer->stopAudio() ...");
|
||||
mPlayer->stopAudio();
|
||||
LOGI("PlaybackEngine: mPlayer->stopAudio() done.");
|
||||
delete mPlayer;
|
||||
mPlayer = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMusicLock);
|
||||
closeMusicFile();
|
||||
}
|
||||
markerPos.store(0.0);
|
||||
duration.store(0.0);
|
||||
@@ -476,10 +499,9 @@ void PlaybackEngine::stop() {
|
||||
PlaybackEngine::~PlaybackEngine() {
|
||||
LOGI("~PlaybackEngine()");
|
||||
closeRubberBand();
|
||||
if (mPlayer) {
|
||||
mPlayer->stopAudio();
|
||||
delete mPlayer;
|
||||
mPlayer = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMusicLock);
|
||||
closeMusicFile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,18 +512,25 @@ void PlaybackEngine::playBeat() {
|
||||
void PlaybackEngine::playMusic(int fd, long long offset, long long length) {
|
||||
if(!mPlayer) return;
|
||||
LOGI("PlaybackEngine::playMusic(fd=%d)", fd);
|
||||
if(musicFile) {
|
||||
// when changing tracks in the UI, close the previous file
|
||||
closeMusicFile();
|
||||
}
|
||||
android_fd = fd;
|
||||
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);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMusicLock);
|
||||
|
||||
if (musicFile) {
|
||||
if (fd == android_fd)
|
||||
android_fd = 0; // HACK: avoid closing the 'fd' if re-opening the same file
|
||||
// when changing tracks in the UI, close the previous file
|
||||
closeMusicFile();
|
||||
}
|
||||
android_fd = fd;
|
||||
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);
|
||||
@@ -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");
|
||||
// so that we may play again after "final chunk"
|
||||
closeRubberBand();
|
||||
// do not closeMusicFile() here, it would close the very file we want to be playing
|
||||
initRubberBand();
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ private:
|
||||
RubberBand::RubberBandStretcher stretcher;
|
||||
MixingPlayer *mPlayer;
|
||||
std::string mFilesDir;
|
||||
std::mutex mMusicLock;
|
||||
std::unique_ptr<MP3File> musicFile;
|
||||
std::atomic<bool> haveMusicFile;
|
||||
std::unique_ptr<std::thread> musicFeed;
|
||||
|
||||
Reference in New Issue
Block a user