-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add AudioEncoderCocoa class, an AudioToolbox implementation of WebCodec's AudioEncoder #37699
Add AudioEncoderCocoa class, an AudioToolbox implementation of WebCodec's AudioEncoder #37699
Conversation
EWS run on previous version of this PR (hash 9224027) |
9224027
to
e61db94
Compare
EWS run on previous version of this PR (hash e61db94) |
e61db94
to
e3088e4
Compare
EWS run on previous version of this PR (hash e3088e4) |
e3088e4
to
71de9c9
Compare
EWS run on previous version of this PR (hash 71de9c9) |
71de9c9
to
f591872
Compare
EWS run on previous version of this PR (hash f591872) |
f591872
to
44a1c71
Compare
EWS run on previous version of this PR (hash 44a1c71) |
44a1c71
to
a3d0d86
Compare
EWS run on previous version of this PR (hash a3d0d86) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments below, I was not able to finish the review.
if (!config.sampleRate || !config.numberOfChannels) | ||
return false; | ||
|
||
if (config.bitrate && *config.bitrate > std::numeric_limits<int>::max()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this fix or should we instead update the WebIDL to add EnforceRange on the various properties instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what the spec states indeed. However this is existing, I could add this as a follow-up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the IDL already has them as [EnforceRange] but this is checking that it's not > int::max() ; while the spec has it has a "long long" I will lodge a WPT bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -84,6 +85,15 @@ static bool isSupportedEncoderCodec(const StringView& codec) | |||
if (!isCodecAllowed) | |||
return false; | |||
|
|||
// Can't find restrictions for those values, but this is what Firefox/Gecko does and WPT audio-encoder-config.https.any.html checks for. | |||
if (config.sampleRate < 3000 || config.sampleRate > 384000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be tempted to file a bug and do that as a follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then we will fail both wpt tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -288,7 +289,7 @@ String InternalAudioDecoderCocoa::initialize(const String& codecName, const Audi | |||
|
|||
m_inputFormatDescription = createAudioFormatDescription(*m_inputDescription, m_codecDescription.span()); | |||
|
|||
m_outputDescription = CAAudioStreamDescription { double(config.sampleRate), uint32_t(config.numberOfChannels), AudioStreamDescription::Float32, CAAudioStreamDescription::IsInterleaved::No }; | |||
m_outputDescription = CAAudioStreamDescription { double(config.sampleRate), uint32_t(config.numberOfChannels), AudioStreamDescription::Float32, CAAudioStreamDescription::IsInterleaved::Yes }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably file a spec bug, can you do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the spec lets the UA choose, there's no option to tell what you want out which is unfortunate.
In theory, how values are packed internally should be an implementation details and it should behave the same regardless.
However, copyTo
has a very different behaviour if the origin is interleaved or not where a planeIndex of 0 now means "all planes"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've opened w3c/webcodecs#859
asbd.mFramesPerPacket = m_config.sampleRate * m_frameDuration->toDouble(); | ||
m_outputDescription = asbd; | ||
} | ||
m_codecString = codecName.isolatedCopy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need this, or could we use an enumeration instead?
That could prevent issues with Strings...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to change this as a follow-up, right now that's pre-existing and how the GStreamer implementation uses it. Don't want to touch it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we probably don't need the isolatedCopy but we generate the activeConfiguration
on the encode workqueue. So the strings needs to work across threads.
a3d0d86
to
c860488
Compare
EWS run on previous version of this PR (hash c860488) |
c860488
to
76211bc
Compare
EWS run on previous version of this PR (hash 76211bc) |
76211bc
to
27cefcf
Compare
EWS run on previous version of this PR (hash 27cefcf) |
27cefcf
to
2b0ba8e
Compare
EWS run on previous version of this PR (hash 2b0ba8e) |
|
||
bool m_isClosed WTF_GUARDED_BY_CAPABILITY(queueSingleton()) { false }; | ||
const AudioEncoder::Config m_config; | ||
String m_codecString; // set during initialisation, const after. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not great to use strings with work queues, I would tend to use an enum instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had planned to do it in a followup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed it fully, WebCodecsAudioEncoder turned out to copy the original string if none is provided in the new description
std::optional<MediaTime> m_frameDuration; // set during initialisation, const after. | ||
std::optional<unsigned> m_complexity; // set during initialisation, const after. | ||
std::optional<unsigned> m_packetlossperc; // set during initialisation, const after. | ||
std::optional<bool> m_useinbandfec; // set during initialisation, const after. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to have a function that returns all these parameters and then we create the InternalAudioEncoderCocoa.
That way, we can make them const.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that was more effort than worth imho.
but done
std::optional<unsigned> m_packetlossperc; // set during initialisation, const after. | ||
std::optional<bool> m_useinbandfec; // set during initialisation, const after. | ||
std::optional<CAAudioStreamDescription> m_inputDescription WTF_GUARDED_BY_CAPABILITY(queueSingleton()); | ||
bool m_isADTS { false }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably set at init time. Maybe add the same comment or make it atomic otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not used yet. I'll add a link to the bwo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed it completely for now
CMBlockBufferRef rawBuffer = PAL::CMSampleBufferGetDataBuffer(cmSample.get()); | ||
ASSERT(rawBuffer); | ||
RetainPtr buffer = rawBuffer; | ||
// Make sure block buffer is contiguous. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have this pattern in a few place, it might be handy to add a utility someday.
if (!PAL::CMBlockBufferIsRangeContiguous(rawBuffer, 0, 0)) { | ||
CMBlockBufferRef contiguousBuffer; | ||
if (auto error = PAL::CMBlockBufferCreateContiguous(nullptr, rawBuffer, nullptr, nullptr, 0, 0, 0, &contiguousBuffer)) { | ||
RELEASE_LOG_ERROR(MediaStream, "Couldn't create buffer with error %d", error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we error the encoder in this case (and below) by rejecting the encode native promise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it can ever happen. It will OOM first
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
return EncodePromise::createAndReject("Couldn't retrieve AudioData's basic description"_s); | ||
|
||
if (*asbd != m_inputDescription) { | ||
if (m_converter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (m_converter) | |
if (auto converter = std::exchange(m_converter, { })) |
|
||
InternalAudioEncoderCocoa::queueSingleton().dispatch([weakEncoder = ThreadSafeWeakPtr { *encoder }] { | ||
if (auto strongEncoder = weakEncoder.get()) | ||
strongEncoder->processEncodedOutputs(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not clear to me that processEncodedOutputs()
will be executed before we resolve the encode native promise. Maybe we should keep the native promise producer somewhere and resolve/reject as part of processEncodedOutputs (and handle the case where processEncodedOutputs is not called).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is always called before because we return the promise itself which is resolved after the frames have returned. Same pattern as AudioDecoder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a call to processEncodedOutput() at the completion of the AudioSampleBufferConverter promise to remove all doubts.
return GenericPromise::createAndResolve(); // No frame encoded yet, nothing to flush; | ||
return converter()->drain()->whenSettled(queueSingleton(), [protectedThis = Ref { *this }] { | ||
assertIsCurrent(queueSingleton()); | ||
protectedThis->processEncodedOutputs(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this fails, maybe we should fail the flush promise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This promise is a no op in practice it can’t fail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
2b0ba8e
to
85d240d
Compare
EWS run on current version of this PR (hash 85d240d) |
…ec's AudioEncoder https://bugs.webkit.org/show_bug.cgi?id=284019 rdar://140889671 Reviewed by Youenn Fablet. We add AudioEncoderCocoa, AudioToolbox implementation of WebCodec's AudioEncoder. We only support encoding to Opus and AAC as the framework doesn't provide encoder for Flac, mp3 and vorbis (decoder only) Enabled WPT tests. * LayoutTests/TestExpectations: * LayoutTests/imported/w3c/web-platform-tests/webcodecs/audio-encoder-codec-specific.https.any-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/webcodecs/audio-encoder-config.https.any-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/webcodecs/audio-encoder-config.https.any.worker-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/webcodecs/audio-encoder.https.any-expected.txt: * LayoutTests/platform/glib/TestExpectations: Remove passing expectations, add the remaining ones that do fail. * LayoutTests/platform/glib/imported/w3c/web-platform-tests/webcodecs/audio-encoder-config.https.any-expected.txt: Removed. * LayoutTests/platform/glib/imported/w3c/web-platform-tests/webcodecs/audio-encoder-config.https.any.worker-expected.txt: Removed. * LayoutTests/platform/ios/imported/w3c/web-platform-tests/webcodecs/audio-encoder-codec-specific.https.any-expected.txt: Added. The only failure remaining is related to different default for Opus encoding which yield slightly different values to what expected. The test performs very rough float comparisons. * LayoutTests/platform/ios/imported/w3c/web-platform-tests/webcodecs/audio-encoder.https.any-expected.txt: Added. * LayoutTests/platform/mac/imported/w3c/web-platform-tests/webcodecs/audio-encoder-codec-specific.https.any-expected.txt: Added. * LayoutTests/platform/mac/imported/w3c/web-platform-tests/webcodecs/audio-encoder.https.any-expected.txt: Added. * Source/WebCore/Modules/webcodecs/OpusEncoderConfig.h: * Source/WebCore/Modules/webcodecs/WebCodecsAudioEncoder.cpp: (WebCore::isSupportedEncoderCodec): Add additional checks to ensure that the values provided in the Opus config are sane. While this isn't probably the best place to do so, as this is common for both GStreamer and Cocoa AudioEncoder, it's the simplest. (WebCore::isValidEncoderConfig): Make sure sampleRate and numberOfChannels aren't 0. (WebCore::createAudioEncoderConfig): Increase code readability by using struct's named member initialisation. (WebCore::WebCodecsAudioEncoder::configure): (WebCore::WebCodecsAudioEncoder::encode): Both Firefox and Chromes rejects AudioData that has a different sampleRate or numberOfChannels. While we did the same, we didn't process the errors as per spec which requires that we also change the state to Closed just before queueing the error. (WebCore::WebCodecsAudioEncoder::isConfigSupported): * Source/WebCore/PlatformMac.cmake: * Source/WebCore/SourcesCocoa.txt: * Source/WebCore/WebCore.xcodeproj/project.pbxproj: * Source/WebCore/platform/AudioEncoder.cpp: (WebCore::AudioEncoder::create): Plumb AudioEncoderCocoa. * Source/WebCore/platform/AudioEncoder.h: * Source/WebCore/platform/AudioEncoderActiveConfiguration.h: Add default initialiser to comply with more recent clang version. It allows to make the struct member initialisation optional. * Source/WebCore/platform/audio/cocoa/AudioDecoderCocoa.cpp: (WebCore::InternalAudioDecoderCocoa::initialize): Change default AudioData creation to use interleaved frames instead of planar. A few tests has this expectations, even though this isn't mandated by the specs. * Source/WebCore/platform/audio/cocoa/AudioEncoderCocoa.cpp: Added. (WebCore::InternalAudioEncoderCocoa::create): (WebCore::InternalAudioEncoderCocoa::reset): (WebCore::InternalAudioEncoderCocoa::converter): (WebCore::InternalAudioEncoderCocoa::queueSingleton): (WebCore::AudioEncoderCocoa::create): (WebCore::AudioEncoderCocoa::AudioEncoderCocoa): (WebCore::AudioEncoderCocoa::~AudioEncoderCocoa): (WebCore::AudioEncoderCocoa::encode): (WebCore::AudioEncoderCocoa::flush): (WebCore::AudioEncoderCocoa::reset): (WebCore::AudioEncoderCocoa::close): (WebCore::InternalAudioEncoderCocoa::InternalAudioEncoderCocoa): (WebCore::InternalAudioEncoderCocoa::initialize): (WebCore::InternalAudioEncoderCocoa::compressedAudioOutputBufferCallback): (WebCore::InternalAudioEncoderCocoa::generateDecoderDescriptionFromSample const): (WebCore::InternalAudioEncoderCocoa::activeConfiguration const): (WebCore::InternalAudioEncoderCocoa::processEncodedOutputs): (WebCore::InternalAudioEncoderCocoa::encode): (WebCore::InternalAudioEncoderCocoa::flush): (WebCore::InternalAudioEncoderCocoa::close): * Source/WebCore/platform/audio/cocoa/AudioEncoderCocoa.h: Copied from Source/WebCore/platform/AudioEncoderActiveConfiguration.h. Canonical link: https://commits.webkit.org/287787@main
85d240d
to
c1b41e0
Compare
Committed 287787@main (c1b41e0): https://commits.webkit.org/287787@main Reviewed commits have been landed. Closing PR #37699 and removing active labels. |
c1b41e0
85d240d
🧪 ios-wk2-wpt🧪 api-ios