Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/common-app/src/demos/Record/Record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ const Record: FC = () => {
}, [onPauseRecording, onResumeRecording]);

useEffect(() => {
Recorder.enableFileOutput();
Recorder.enableFileOutput({ rotateIntervalBytes: 1024 * 1024 });

return () => {
Recorder.disableFileOutput();
Expand Down
2 changes: 2 additions & 0 deletions packages/audiodocs/docs/inputs/audio-recorder.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,7 @@ interface OnAudioReadyEventType {
```tsx
interface AudioRecorderFileOptions {
channelCount?: number;
rotateIntervalBytes?: number;

format?: FileFormat;
preset?: FilePresetType;
Expand All @@ -732,6 +733,7 @@ interface AudioRecorderFileOptions {
```

- `channelCount` - The desired channel count in the resulting file. not all file formats supports all possible channel counts.
- `rotateIntervalBytes` - The threshold size (in bytes) at which the recorder will start writing to a new file. If set to 0 (default), file output rotation is disabled. When active, new files are named with the original prefix appended with a timestamp.
- `format` - The desired extension and file format of the recorder file. Check: [FileFormat](#fileformat) below.
- `preset` - The desired recorder file properties, you can use either one of built-in properties or tweak low-level parameters yourself. Check [FilePresetType](#filepresettype) for more details.
- `directory` - Either `FileDirectory.Cache` or `FileDirectory.Document` (default: `FileDirectory.Cache`). Determines the system directory that the file will be saved to.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <android/log.h>
#include <audioapi/android/core/AndroidAudioRecorder.h>
#include <audioapi/android/core/utils/AndroidFileWriterBackend.h>
#include <audioapi/android/core/utils/AndroidRecorderCallback.h>
Expand All @@ -10,6 +11,7 @@
#include <audioapi/core/sources/RecorderAdapterNode.h>
#include <audioapi/core/utils/Constants.h>
#include <audioapi/core/utils/Locker.h>
#include <audioapi/core/utils/RotatingFileWriter.h>
#include <audioapi/events/AudioEventHandlerRegistry.h>
#include <audioapi/utils/AudioArray.h>
#include <audioapi/utils/AudioBus.h>
Expand Down Expand Up @@ -122,19 +124,68 @@ Result<std::string, std::string> AndroidAudioRecorder::start(const std::string &
}

if (usesFileOutput()) {
auto fileResult = std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
->openFile(
streamSampleRate_,
streamChannelCount_,
streamMaxBufferSizeInFrames_,
fileNameOverride);
auto createWriter =
[this](
const std::shared_ptr<AudioFileProperties> &props) -> std::shared_ptr<AudioFileWriter> {
if (props->format == AudioFileProperties::Format::WAV) {
return std::make_shared<MiniAudioFileWriter>(
audioEventHandlerRegistry_,
props,
streamSampleRate_,
streamChannelCount_,
streamMaxBufferSizeInFrames_);
} else {
#if !RN_AUDIO_API_FFMPEG_DISABLED
return std::make_shared<android::ffmpeg::FFmpegAudioFileWriter>(
audioEventHandlerRegistry_,
props,
streamSampleRate_,
streamChannelCount_,
streamMaxBufferSizeInFrames_);
#else
return nullptr;
#endif
}
};

if (fileProperties_->rotateIntervalBytes > 0) {
if (createWriter(fileProperties_) == nullptr) {
return Result<std::string, std::string>::Err(
"FFmpeg backend is disabled. Cannot create file writer for the requested format. Use "
"WAV "
"format instead.");
}

fileWriter_ = std::make_shared<RotatingFileWriter>(
audioEventHandlerRegistry_,
fileProperties_,
fileProperties_->rotateIntervalBytes,
createWriter);
} else {
fileWriter_ = createWriter(fileProperties_);
if (fileWriter_ == nullptr) {
return Result<std::string, std::string>::Err(
"FFmpeg backend is disabled. Cannot create file writer for the requested format. Use "
"WAV "
"format instead.");
}
}

fileWriter_->setOnErrorCallback(errorCallbackId_.load(std::memory_order_acquire));

auto fileResult = fileWriter_->openFile();

if (!fileResult.is_ok()) {
return Result<std::string, std::string>::Err(
"Failed to open file for writing: " + fileResult.unwrap_err());
}

filePath_ = fileResult.unwrap();
__android_log_print(
ANDROID_LOG_INFO,
"AndroidAudioRecorder",
"File created successfully at path: %s",
filePath_.c_str());
}

if (usesCallback()) {
Expand Down Expand Up @@ -217,23 +268,56 @@ Result<std::tuple<std::string, double, double>, std::string> AndroidAudioRecorde
Result<std::string, std::string> AndroidAudioRecorder::enableFileOutput(
std::shared_ptr<AudioFileProperties> properties) {
std::scoped_lock fileWriterLock(fileWriterMutex_);
fileProperties_ = properties;

if (properties->format == AudioFileProperties::Format::WAV) {
fileWriter_ = std::make_shared<MiniAudioFileWriter>(audioEventHandlerRegistry_, properties);
} else {
if (!isIdle()) {
auto createWriter =
[this](
const std::shared_ptr<AudioFileProperties> &props) -> std::shared_ptr<AudioFileWriter> {
if (props->format == AudioFileProperties::Format::WAV) {
return std::make_shared<MiniAudioFileWriter>(
audioEventHandlerRegistry_,
props,
streamSampleRate_,
streamChannelCount_,
streamMaxBufferSizeInFrames_);
} else {
#if !RN_AUDIO_API_FFMPEG_DISABLED
fileWriter_ = std::make_shared<android::ffmpeg::FFmpegAudioFileWriter>(
audioEventHandlerRegistry_, properties);
return std::make_shared<android::ffmpeg::FFmpegAudioFileWriter>(
audioEventHandlerRegistry_,
props,
streamSampleRate_,
streamChannelCount_,
streamMaxBufferSizeInFrames_);
#else
return Result<std::string, std::string>::Err(
"FFmpeg backend is disabled. Cannot create file writer for the requested format. Use WAV format instead.");
return nullptr;
#endif
}
}
};

if (properties->rotateIntervalBytes > 0) {
if (createWriter(properties) == nullptr) {
return Result<std::string, std::string>::Err(
"FFmpeg backend is disabled. Cannot create file writer for the requested format. Use "
"WAV "
"format instead.");
}

if (!isIdle()) {
auto fileResult =
std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
->openFile(streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_, "");
fileWriter_ = std::make_shared<RotatingFileWriter>(
audioEventHandlerRegistry_, properties, properties->rotateIntervalBytes, createWriter);
} else {
fileWriter_ = createWriter(properties);
if (fileWriter_ == nullptr) {
return Result<std::string, std::string>::Err(
"FFmpeg backend is disabled. Cannot create file writer for the requested format. Use "
"WAV "
"format instead.");
}
}

fileWriter_->setOnErrorCallback(errorCallbackId_.load(std::memory_order_acquire));

auto fileResult = fileWriter_->openFile();

if (!fileResult.is_ok()) {
return Result<std::string, std::string>::Err(
Expand Down Expand Up @@ -360,8 +444,7 @@ oboe::DataCallbackResult AndroidAudioRecorder::onAudioReady(

if (usesFileOutput()) {
if (auto fileWriterLock = Locker::tryLock(fileWriterMutex_)) {
std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
->writeAudioData(audioData, numFrames);
fileWriter_->writeAudioData(audioData, numFrames);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@ class AndroidFileWriterBackend : public AudioFileWriter {
public:
explicit AndroidFileWriterBackend(
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
const std::shared_ptr<AudioFileProperties> &fileProperties)
: AudioFileWriter(audioEventHandlerRegistry, fileProperties) {}
const std::shared_ptr<AudioFileProperties> &fileProperties,
float streamSampleRate, int32_t streamChannelCount, int32_t streamMaxBufferSize)
: AudioFileWriter(audioEventHandlerRegistry, fileProperties),
streamSampleRate_(streamSampleRate),
streamChannelCount_(streamChannelCount),
streamMaxBufferSize_(streamMaxBufferSize) {}

virtual OpenFileResult openFile(float streamSampleRate, int32_t streamChannelCount, int32_t streamMaxBufferSize, const std::string &fileNameOverride) = 0;
virtual bool writeAudioData(void *data, int numFrames) = 0;
OpenFileResult openFile() override = 0;
bool writeAudioData(void *data, int numFrames) override = 0;

std::string getFilePath() const override { return filePath_; }
double getCurrentDuration() const override { return static_cast<double>(framesWritten_.load(std::memory_order_acquire)) / streamSampleRate_; }
size_t getFileSizeBytes() const override { return 0; }

protected:
float streamSampleRate_{0};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extern "C" {
#include <audioapi/utils/AudioFileProperties.h>
#include <audioapi/utils/UnitConversion.h>

#include <sys/stat.h>
#include <algorithm>
#include <cassert>
#include <memory>
Expand All @@ -30,8 +31,16 @@ namespace audioapi::android::ffmpeg {

FFmpegAudioFileWriter::FFmpegAudioFileWriter(
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
const std::shared_ptr<AudioFileProperties> &fileProperties)
: AndroidFileWriterBackend(audioEventHandlerRegistry, fileProperties) {
const std::shared_ptr<AudioFileProperties> &fileProperties,
float streamSampleRate,
int32_t streamChannelCount,
int32_t streamMaxBufferSize)
: AndroidFileWriterBackend(
audioEventHandlerRegistry,
fileProperties,
streamSampleRate,
streamChannelCount,
streamMaxBufferSize) {
// Set flush interval from properties, limit minimum to 100ms
// to avoid people hurting themselves too much
flushIntervalMs_ = std::max(fileProperties_->androidFlushIntervalMs, defaultFlushInterval);
Expand All @@ -50,19 +59,10 @@ FFmpegAudioFileWriter::~FFmpegAudioFileWriter() {
/// @param streamChannelCount The number of channels in the incoming audio stream.
/// @param streamMaxBufferSize The estimated maximum buffer size for the incoming audio stream.
/// @returns Success status with file path or Error status with message.
OpenFileResult FFmpegAudioFileWriter::openFile(
float streamSampleRate,
int32_t streamChannelCount,
int32_t streamMaxBufferSize,
const std::string &fileNameOverride) {
streamSampleRate_ = streamSampleRate;
streamChannelCount_ = streamChannelCount;
streamMaxBufferSize_ = streamMaxBufferSize;
OpenFileResult FFmpegAudioFileWriter::openFile() {
framesWritten_.store(0, std::memory_order_release);
nextPts_ = 0;
Result<NoneType, std::string> result = Result<NoneType, std::string>::Ok(None);
Result<std::string, std::string> filePathResult =
fileoptions::getFilePath(fileProperties_, fileNameOverride);
auto filePathResult = fileoptions::getFilePath(fileProperties_, "");

if (!filePathResult.is_ok()) {
return OpenFileResult::Err(filePathResult.unwrap_err());
Expand All @@ -80,11 +80,10 @@ OpenFileResult FFmpegAudioFileWriter::openFile(
.and_then([this, codec](auto) { return configureAndOpenCodec(codec); })
.and_then([this](auto) { return initializeStream(); })
.and_then([this](auto) { return openIOAndWriteHeader(); })
.and_then([this, streamSampleRate, streamChannelCount](auto) {
return initializeResampler(streamSampleRate, streamChannelCount);
})
.and_then([this, streamMaxBufferSize, filePath = std::move(filePath_)](auto) {
initializeBuffers(streamMaxBufferSize);
.and_then(
[this](auto) { return initializeResampler(streamSampleRate_, streamChannelCount_); })
.and_then([this, filePath = std::move(filePath_)](auto) {
initializeBuffers(streamMaxBufferSize_);
isFileOpen_.store(true, std::memory_order_release);
return OpenFileResult::Ok(filePath);
});
Expand Down Expand Up @@ -242,6 +241,23 @@ Result<NoneType, std::string> FFmpegAudioFileWriter::openIOAndWriteHeader() {
return Result<NoneType, std::string>::Ok(None);
}

size_t FFmpegAudioFileWriter::getFileSizeBytes() const {
if (formatCtx_ == nullptr) {
return 0;
}

if (formatCtx_ && formatCtx_->pb) {
return static_cast<size_t>(avio_tell(formatCtx_->pb));
}

// Fallback
struct stat st;
if (stat(filePath_.c_str(), &st) == 0) {
return st.st_size;
}
return 0;
}

/// @brief Initializes the resampler context for audio conversion.
/// @param inputRate The sample rate of the input audio.
/// @param inputChannels The number of channels in the input audio.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ class FFmpegAudioFileWriter : public AndroidFileWriterBackend {
public:
explicit FFmpegAudioFileWriter(
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
const std::shared_ptr<AudioFileProperties> &fileProperties);
const std::shared_ptr<AudioFileProperties> &fileProperties,
float streamSampleRate, int32_t streamChannelCount, int32_t streamMaxBufferSize);
~FFmpegAudioFileWriter();

OpenFileResult openFile(float streamSampleRate, int32_t streamChannelCount, int32_t streamMaxBufferSize, const std::string &fileNameOverride) override;
OpenFileResult openFile() override;
CloseFileResult closeFile() override;

bool writeAudioData(void *data, int numFrames) override;
size_t getFileSizeBytes() const override;

private:
av_unique_ptr<AVCodecContext> encoderCtx_{nullptr};
Expand Down
Loading