diff --git a/app/src/main/cpp/PlaybackEngine.cpp b/app/src/main/cpp/PlaybackEngine.cpp index f7a029d..b19a925 100644 --- a/app/src/main/cpp/PlaybackEngine.cpp +++ b/app/src/main/cpp/PlaybackEngine.cpp @@ -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 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 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(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(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(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(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(*(reinterpret_cast(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(*(reinterpret_cast(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 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 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 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(); } diff --git a/app/src/main/cpp/PlaybackEngine.h b/app/src/main/cpp/PlaybackEngine.h index de5b249..4e72c94 100644 --- a/app/src/main/cpp/PlaybackEngine.h +++ b/app/src/main/cpp/PlaybackEngine.h @@ -62,6 +62,7 @@ private: RubberBand::RubberBandStretcher stretcher; MixingPlayer *mPlayer; std::string mFilesDir; + std::mutex mMusicLock; std::unique_ptr musicFile; std::atomic haveMusicFile; std::unique_ptr musicFeed;