Skip to content

feat: encoder - #1183

Open
mdydek wants to merge 10 commits into
mainfrom
feat/encoder-interface
Open

feat: encoder#1183
mdydek wants to merge 10 commits into
mainfrom
feat/encoder-interface

Conversation

@mdydek

@mdydek mdydek commented Jul 21, 2026

Copy link
Copy Markdown
Member

Closes #

⚠️ Breaking changes ⚠️

Introduced changes

Checklist

  • Linked relevant issue
  • Updated relevant documentation
  • Added/Conducted relevant tests
  • Performed self-review of the code
  • Updated Web Audio API coverage
  • Added support for web
  • Updated old arch android spec file

mdydek and others added 4 commits July 21, 2026 13:19
Resolves conflicts between the encoder work and the OS-APIs decoding
refactor (#1177):
- AudioFileConcatenator: main's decoder-factory WAV path + encoder's
  OS remux (M4A/MP4) path; all FFmpeg remux code removed
- AudioEventHandlerRegistry -> IAudioEventHandlerRegistry (#1212) in
  encoder file writers
- AndroidEncoding/AndroidRemux moved to android/src/main/cpp/audioapi/android/
  to match AndroidDecoding placement
- docs/flags updated: recording and concatAudioFiles are FFmpeg-free

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mdydek mdydek changed the title feat: 1st version of encoder feat: encoder Aug 12, 2026
@closetcaiman closetcaiman added the feature New user-facing features or major capabilities label Aug 12, 2026
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

WPT non-regression comparison

ERROR — the comparison did not produce a report; the test run itself likely failed.

Workflow run · this comment is updated on every push.

@mdydek
mdydek marked this pull request as ready for review August 18, 2026 08:35

@closetcaiman closetcaiman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add description to the PR for fast-access reference to the scope of these changes.

Comment on lines +91 to 107
// Packs @p numberOfChannels planar channel pointers into one channel-interleaved buffer.
// @p outputInterleaved must hold numberOfFrames * numberOfChannels floats.
void interleave(
const float *const *inputChannels,
size_t numberOfChannels,
float *outputInterleaved,
size_t numberOfFrames);

// Splits a channel-interleaved buffer into @p numberOfChannels planar channel pointers,
// each of which must hold numberOfFrames floats.
void deinterleave(
const float *inputInterleaved,
float *const *outputChannels,
size_t numberOfChannels,
size_t numberOfFrames);

} // namespace audioapi::dsp

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intended to be reused for (de)interleaving AudioBuffers?

Comment on lines +58 to +73
void RotatingFileWriter::writeAudioData(const float *interleavedFrames, int numFrames) {
if (currentWriter_ == nullptr) {
return;
}

currentWriter_->writeAudioData(interleavedFrames, numFrames);

writesSinceLastCheck_++;
if (writesSinceLastCheck_ >= FILE_SIZE_CHECK_WRITE_INTERVAL) {
writesSinceLastCheck_ = 0;
if (currentWriter_->getFileSizeBytes() > rotateIntervalBytes_) {
rotateFiles();
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we ignore the result here? Should we fire an event? This seems really dangerous to me.

Comment on lines +39 to +63
std::vector<EncoderOutputSpec> EncoderCapabilities::probe() {
#ifdef __APPLE__
return {
{.container = AudioContainer::WAV, .codec = AudioCodec::PCM, .extension = "wav"},
{.container = AudioContainer::CAF, .codec = AudioCodec::PCM, .extension = "caf"},
{.container = AudioContainer::AIFF, .codec = AudioCodec::PCM, .extension = "aiff"},
{.container = AudioContainer::M4A, .codec = AudioCodec::AAC, .extension = "m4a"},
{.container = AudioContainer::M4A, .codec = AudioCodec::ALAC, .extension = "m4a"},
{.container = AudioContainer::FLAC, .codec = AudioCodec::FLAC, .extension = "flac"},
{.container = AudioContainer::WAV, .codec = AudioCodec::ULAW, .extension = "wav"},
{.container = AudioContainer::WAV, .codec = AudioCodec::ALAW, .extension = "wav"},
};
#elif defined(__ANDROID__)
return {
{.container = AudioContainer::WAV, .codec = AudioCodec::PCM, .extension = "wav"},
{.container = AudioContainer::M4A, .codec = AudioCodec::AAC, .extension = "m4a"},
{.container = AudioContainer::FLAC, .codec = AudioCodec::FLAC, .extension = "flac"},
{.container = AudioContainer::OGG, .codec = AudioCodec::OPUS, .extension = "ogg"},
{.container = AudioContainer::WEBM, .codec = AudioCodec::OPUS, .extension = "webm"},
{.container = AudioContainer::WEBM, .codec = AudioCodec::VORBIS, .extension = "webm"},
};
#else
return {};
#endif
}

@closetcaiman closetcaiman Aug 21, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probe name seems misleading as we are not actually probing anything here. Maybe this could be just a static field?

Comment on lines +11 to +38
EncoderOutputSpec EncoderCapabilities::specForFormat(Format format) {
switch (format) {
case Format::WAV:
return {.container = AudioContainer::WAV, .codec = AudioCodec::PCM, .extension = "wav"};
case Format::CAF:
return {.container = AudioContainer::CAF, .codec = AudioCodec::PCM, .extension = "caf"};
case Format::M4A:
return {.container = AudioContainer::M4A, .codec = AudioCodec::AAC, .extension = "m4a"};
case Format::FLAC:
return {.container = AudioContainer::FLAC, .codec = AudioCodec::FLAC, .extension = "flac"};
case Format::AIFF:
return {.container = AudioContainer::AIFF, .codec = AudioCodec::PCM, .extension = "aiff"};
case Format::ALAC:
return {.container = AudioContainer::M4A, .codec = AudioCodec::ALAC, .extension = "m4a"};
case Format::OPUS_OGG:
return {.container = AudioContainer::OGG, .codec = AudioCodec::OPUS, .extension = "ogg"};
case Format::OPUS_WEBM:
return {.container = AudioContainer::WEBM, .codec = AudioCodec::OPUS, .extension = "webm"};
case Format::VORBIS_WEBM:
return {.container = AudioContainer::WEBM, .codec = AudioCodec::VORBIS, .extension = "webm"};
case Format::ULAW:
return {.container = AudioContainer::WAV, .codec = AudioCodec::ULAW, .extension = "wav"};
case Format::ALAW:
return {.container = AudioContainer::WAV, .codec = AudioCodec::ALAW, .extension = "wav"};
}
return {.container = AudioContainer::WAV, .codec = AudioCodec::PCM, .extension = "wav"};
}

@closetcaiman closetcaiman Aug 21, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same case as with probe. We can just hardcode this, then EncoderCapabilites can probably be just a namespace.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getFormat is not used anymore, same for getFileSettings.

Comment on lines +21 to +39
OpenFileResult RotatingFileWriter::openFile(
float streamSampleRate,
int32_t streamChannelCount,
int32_t maxFramesPerBuffer,
const std::string &fileNameOverride) {
streamSampleRate_ = streamSampleRate;
streamChannelCount_ = streamChannelCount;
maxFramesPerBuffer_ = maxFramesPerBuffer;

if (!fileNameOverride.empty()) {
fileProperties_->fileNamePrefix = fileNameOverride + fileProperties_->fileNamePrefix;
}
if (currentWriter_ == nullptr) {
currentWriter_ = writerFactory_(fileProperties_);
}

return openInnerWriter();
}

@closetcaiman closetcaiman Aug 21, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not idempotent in terms of fileProperties_->fileNamePrefix. If I understand the flow correctly, re-preparing can create a weird-concatented name.

Comment on lines +29 to +36
if (usesFileOutput()) {
auto fileWriterLock = Locker::tryLock(fileWriterMutex_);
if (fileWriterLock && fileWriter_) {
// if we are inside the lock and the fileWriter_ is valid we can be sure that nobody will interrupt us in the middle
fileWriter_->writeAudioData(interleavedFrames, numFrames);
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call chain causes the file rotation to be invoked on AudioThread.

@maciejmakowski2003 maciejmakowski2003 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add an overview of refactor? would be great to get some diagram

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New user-facing features or major capabilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants