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/AudioBuffer.h>
Expand Down Expand Up @@ -122,19 +124,15 @@ 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);

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

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 @@ -218,33 +216,69 @@ 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 writerResult = setupFileWriter(properties);
if (!writerResult.is_ok()) {
return writerResult;
}
}

fileOutputEnabled_.store(true, std::memory_order_release);
return Result<std::string, std::string>::Ok(filePath_);
}

std::shared_ptr<AudioFileWriter> AndroidAudioRecorder::createFileWriter(
const std::shared_ptr<AudioFileProperties> &props) {
if (props->format == AudioFileProperties::Format::WAV) {
return std::make_shared<MiniAudioFileWriter>(
audioEventHandlerRegistry_,
props,
streamSampleRate_,
streamChannelCount_,
streamMaxBufferSizeInFrames_);
}
#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 nullptr;
#endif
}

Result<std::string, std::string> AndroidAudioRecorder::setupFileWriter(
const std::shared_ptr<AudioFileProperties> &properties) {
#if RN_AUDIO_API_FFMPEG_DISABLED
if (properties->format != AudioFileProperties::Format::WAV) {
return Result<std::string, std::string>::Err(
"FFmpeg backend is disabled. Cannot create file writer for the requested format. Use WAV format instead.");
#endif
}
#endif

if (!isIdle()) {
auto fileResult =
std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
->openFile(streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_, "");
if (properties->rotateIntervalBytes > 0) {
fileWriter_ = std::make_shared<RotatingFileWriter>(
audioEventHandlerRegistry_,
properties,
properties->rotateIntervalBytes,
[this](const std::shared_ptr<AudioFileProperties> &p) { return createFileWriter(p); });
} else {
fileWriter_ = createFileWriter(properties);
}

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

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

fileOutputEnabled_.store(true, std::memory_order_release);
filePath_ = fileResult.unwrap();
return Result<std::string, std::string>::Ok(filePath_);
}

Expand Down Expand Up @@ -362,8 +396,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 @@ -65,6 +65,8 @@ class AndroidAudioRecorder : public oboe::AudioStreamCallback, public AudioRecor

std::shared_ptr<oboe::AudioStream> mStream_;
Result<NoneType, std::string> openAudioStream();
std::shared_ptr<AudioFileWriter> createFileWriter(const std::shared_ptr<AudioFileProperties> &props);
Result<std::string, std::string> setupFileWriter(const std::shared_ptr<AudioFileProperties> &properties);
};

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,19 @@ class AndroidFileWriterBackend : public AudioFileWriter {
public:
explicit AndroidFileWriterBackend(
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)
: 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;
void writeAudioData(void *data, int numFrames);
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 @@ -89,11 +89,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 @@ -247,6 +246,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_ != nullptr && formatCtx_->pb != nullptr) {
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,12 +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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <audioapi/utils/AudioFileProperties.h>
#include <audioapi/utils/UnitConversion.h>

#include <sys/stat.h>
#include <cstdio>
#include <memory>
#include <string>
Expand Down Expand Up @@ -40,8 +41,16 @@ inline ma_format getDataFormat(const std::shared_ptr<AudioFileProperties> &prope

MiniAudioFileWriter::MiniAudioFileWriter(
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) {}

MiniAudioFileWriter::~MiniAudioFileWriter() {
isFileOpen_.store(false, std::memory_order_release);
Expand Down Expand Up @@ -72,14 +81,7 @@ MiniAudioFileWriter::~MiniAudioFileWriter() {
/// @param streamChannelCount The channel count of the incoming audio stream.
/// @param streamMaxBufferSize The maximum buffer size of the incoming audio stream.
/// @return The status of the file opening operation.
OpenFileResult MiniAudioFileWriter::openFile(
float streamSampleRate,
int32_t streamChannelCount,
int32_t streamMaxBufferSize,
const std::string &fileNameOverride) {
streamSampleRate_ = streamSampleRate;
streamChannelCount_ = streamChannelCount;
streamMaxBufferSize_ = streamMaxBufferSize;
OpenFileResult MiniAudioFileWriter::openFile() {
ma_result result;
framesWritten_.store(0, std::memory_order_release);

Expand All @@ -96,7 +98,7 @@ OpenFileResult MiniAudioFileWriter::openFile(
"Failed to initialize converter" + std::string(ma_result_description(result)));
}

result = initializeEncoder(fileNameOverride);
result = initializeEncoder();

if (result != MA_SUCCESS) {
return OpenFileResult ::Err(
Expand Down Expand Up @@ -174,6 +176,16 @@ CloseFileResult MiniAudioFileWriter::closeFile() {
return CloseFileResult ::Ok({fileSizeInMB, durationInSeconds});
}

/// @brief Get the current file size in bytes.
/// @return The size of the file in bytes.
size_t MiniAudioFileWriter::getFileSizeBytes() const {
struct stat st;
if (stat(filePath_.c_str(), &st) == 0) {
return st.st_size;
}
return 0;
}

/// @brief Writes audio data to the file.
/// If possible (sample format, channel count, and interleaving matches),
/// the data is written directly, otherwise in-memory conversion is performed first
Expand Down Expand Up @@ -273,10 +285,10 @@ ma_result MiniAudioFileWriter::initializeConverterIfNeeded() {
/// This method sets up the audio encoder for writing to the file,
/// it should be called only on the JS thread. (during file opening)
/// @return MA_SUCCESS if initialization was successful, otherwise an error code.
ma_result MiniAudioFileWriter::initializeEncoder(const std::string &fileNameOverride) {
ma_result MiniAudioFileWriter::initializeEncoder() {
ma_result result;
Result<std::string, std::string> filePathResult =
android::fileoptions::getFilePath(fileProperties_, fileNameOverride);
android::fileoptions::getFilePath(fileProperties_, "");

if (!filePathResult.is_ok()) {
return MA_ERROR;
Expand Down
Loading