blob: 03b9f544979acfda4562240be9811eb572370228 [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuchsia/media/cpp/fidl.h>
#include <mutex>
#include <utility>
#include <gtest/gtest.h>
#include "src/media/codec/codecs/sw/aac/codec_adapter_aac_encoder.h"
#include "src/media/codec/codecs/test/test_codec_packets.h"
#include "src/media/codec/codecs/test/test_fake_codec_adapter_events.h"
namespace {
fuchsia::media::FormatDetails MakeValidAacEncoderFormatDetails() {
fuchsia::media::PcmFormat pcm;
pcm.pcm_mode = fuchsia::media::AudioPcmMode::LINEAR;
pcm.bits_per_sample = 16;
pcm.frames_per_second = 48000;
pcm.channel_map = {fuchsia::media::AudioChannelId::LF};
fuchsia::media::AudioUncompressedFormat uncompressed;
uncompressed.set_pcm(std::move(pcm));
fuchsia::media::AudioFormat audio;
audio.set_uncompressed(std::move(uncompressed));
fuchsia::media::DomainFormat domain;
domain.set_audio(std::move(audio));
fuchsia::media::AacEncoderSettings aac_settings;
aac_settings.aot = fuchsia::media::AacAudioObjectType::MPEG4_AAC_LC;
aac_settings.channel_mode = fuchsia::media::AacChannelMode::MONO;
aac_settings.bit_rate = fuchsia::media::AacBitRate::WithConstant(
fuchsia::media::AacConstantBitRate{.bit_rate = 64000});
aac_settings.transport = fuchsia::media::AacTransport::WithRaw(fuchsia::media::AacTransportRaw{});
fuchsia::media::EncoderSettings encoder_settings;
encoder_settings.set_aac(std::move(aac_settings));
fuchsia::media::FormatDetails format_details;
format_details.set_format_details_version_ordinal(0);
format_details.set_domain(std::move(domain));
format_details.set_encoder_settings(std::move(encoder_settings));
return format_details;
}
TEST(CodecAdapterAacEncoderTest, EmptyStreamEndOfStreamAndPostStopOutputConstraints) {
std::mutex lock;
FakeCodecAdapterEvents events;
CodecAdapterAacEncoder encoder(lock, &events);
auto format_details = MakeValidAacEncoderFormatDetails();
encoder.CoreCodecInit(format_details);
ASSERT_EQ(events.fail_codec_count(), 0u);
// Stream 1: Queue EndOfStream immediately without any FormatDetails or Packet.
encoder.CoreCodecStartStream();
encoder.CoreCodecQueueInputEndOfStream();
events.WaitForOutputEosCount(1);
EXPECT_EQ(events.fail_codec_count(), 0u);
EXPECT_EQ(events.output_eos_count(), 1u);
encoder.CoreCodecStopStream();
// Stream 2: Queue FormatDetails and a partial input packet (16 bytes < 2048-byte AAC chunk size)
// so stream_->chunk_input_stream buffers partial bytes, then stop stream without EOS.
encoder.CoreCodecStartStream();
encoder.CoreCodecQueueInputFormatDetails(format_details);
events.WaitForOutputConstraintsChangeCount(1);
encoder.CoreCodecMidStreamOutputBufferReConfigFinish();
auto input_buffers = Buffers({4096});
auto input_packets = Packets(1);
CodecPacket* input_packet = input_packets.ptr(0);
input_packet->SetBuffer(input_buffers.ptr(0));
input_packet->SetStartOffset(0);
input_packet->SetValidLengthBytes(16); // Partial AAC frame (< 2048 bytes)
encoder.CoreCodecQueueInputPacket(input_packet);
events.WaitForInputPacketDoneCount(1);
EXPECT_EQ(events.fail_codec_count(), 0u);
encoder.CoreCodecStopStream();
// Between streams: CoreCodecGetBufferCollectionConstraints2(kOutputPort) must succeed using
// format_configuration_->recommended_output_buffer_size even after stream_ was reset.
fuchsia::media::StreamBufferConstraints stream_constraints;
fuchsia::media::StreamBufferPartialSettings partial_settings;
auto constraints = encoder.CoreCodecGetBufferCollectionConstraints2(
kOutputPort, stream_constraints, partial_settings);
ASSERT_TRUE(constraints.buffer_memory_constraints().has_value());
// kFdkMaxOutBytesPerChannel (6144 / 8 = 768) * 1 channel = 768 bytes.
EXPECT_EQ(constraints.buffer_memory_constraints()->min_size_bytes(), 768u);
// Stream 3: Queue EndOfStream without FormatDetails or Packet; must not flush stale partial
// bytes from Stream 2 or dereference null stream_.
encoder.CoreCodecStartStream();
encoder.CoreCodecQueueInputEndOfStream();
events.WaitForOutputEosCount(2);
EXPECT_EQ(events.fail_codec_count(), 0u);
EXPECT_EQ(events.output_eos_count(), 2u);
EXPECT_EQ(events.output_packet_count(), 0u);
encoder.CoreCodecStopStream();
}
TEST(CodecAdapterAacEncoderTest, QueuedPacketAndEosIgnoredAfterFormatDetailsFailure) {
std::mutex lock;
FakeCodecAdapterEvents events;
CodecAdapterAacEncoder encoder(lock, &events);
auto valid_format_details = MakeValidAacEncoderFormatDetails();
encoder.CoreCodecInit(valid_format_details);
ASSERT_EQ(events.fail_codec_count(), 0u);
encoder.CoreCodecStartStream();
// Queue invalid FormatDetails (missing encoder_settings) followed immediately by an input packet,
// EndOfStream, and a trailing input packet on input_processing_loop_ before CoreCodecStopStream()
// is called.
fuchsia::media::FormatDetails invalid_format_details = MakeValidAacEncoderFormatDetails();
invalid_format_details.clear_encoder_settings();
encoder.CoreCodecQueueInputFormatDetails(invalid_format_details);
auto input_buffers = Buffers({4096, 4096});
auto input_packets = Packets(2);
CodecPacket* input_packet_0 = input_packets.ptr(0);
input_packet_0->SetBuffer(input_buffers.ptr(0));
input_packet_0->SetStartOffset(0);
input_packet_0->SetValidLengthBytes(16);
CodecPacket* input_packet_1 = input_packets.ptr(1);
input_packet_1->SetBuffer(input_buffers.ptr(1));
input_packet_1->SetStartOffset(0);
input_packet_1->SetValidLengthBytes(16);
encoder.CoreCodecQueueInputPacket(input_packet_0);
encoder.CoreCodecQueueInputEndOfStream();
encoder.CoreCodecQueueInputPacket(input_packet_1);
// Wait for both input packets (including the one queued after EndOfStream) to be returned on
// input_processing_loop_ before calling CoreCodecStopStream(), verifying that ProcessInput itself
// deactivated the stream upon FormatDetails failure.
events.WaitForInputPacketDoneCount(2, /*stop_on_fail_codec=*/false);
EXPECT_EQ(events.fail_codec_count(), 1u);
EXPECT_EQ(events.input_packet_done_count(), 2u);
EXPECT_EQ(events.output_eos_count(), 0u);
encoder.CoreCodecStopStream();
}
TEST(CodecAdapterAacEncoderTest, StopAndMidStreamFormatFailureWhileOutputReconfigPending) {
std::mutex lock;
FakeCodecAdapterEvents events;
CodecAdapterAacEncoder encoder(lock, &events);
auto format_details_v0 = MakeValidAacEncoderFormatDetails();
encoder.CoreCodecInit(format_details_v0);
ASSERT_EQ(events.fail_codec_count(), 0u);
auto input_buffers = Buffers({4096, 4096});
auto input_packets = Packets(2);
CodecPacket* input_packet_0 = input_packets.ptr(0);
input_packet_0->SetBuffer(input_buffers.ptr(0));
input_packet_0->SetStartOffset(0);
input_packet_0->SetValidLengthBytes(16);
CodecPacket* input_packet_1 = input_packets.ptr(1);
input_packet_1->SetBuffer(input_buffers.ptr(1));
input_packet_1->SetStartOffset(0);
input_packet_1->SetValidLengthBytes(16);
// Stream 1: Queue FormatDetails (setting output_reconfig_pending_ = true) and a Packet + EOS
// without calling CoreCodecMidStreamOutputBufferReConfigFinish(), then stop the stream.
// CoreCodecStopStream() must unblock the waiting ProcessInput() call without deadlocking.
encoder.CoreCodecStartStream();
encoder.CoreCodecQueueInputFormatDetails(format_details_v0);
events.WaitForOutputConstraintsChangeCount(1);
encoder.CoreCodecQueueInputPacket(input_packet_0);
encoder.CoreCodecQueueInputEndOfStream();
encoder.CoreCodecStopStream();
EXPECT_EQ(events.fail_codec_count(), 0u);
EXPECT_EQ(events.input_packet_done_count(), 1u);
EXPECT_EQ(events.output_eos_count(), 0u);
// Stream 2: Queue FormatDetails(v0) (setting output_reconfig_pending_ = true), followed by a
// midstream FormatDetails(v1) (which fails before ReConfigFinish) and a Packet + EOS.
// ProcessInput() must reset output_reconfig_pending_ on failure so the trailing Packet + EOS
// do not hang waiting on reconfig_cond_.
encoder.CoreCodecStartStream();
encoder.CoreCodecQueueInputFormatDetails(format_details_v0);
events.WaitForOutputConstraintsChangeCount(2);
auto format_details_v1 = MakeValidAacEncoderFormatDetails();
format_details_v1.set_format_details_version_ordinal(1);
encoder.CoreCodecQueueInputFormatDetails(format_details_v1);
encoder.CoreCodecQueueInputPacket(input_packet_1);
encoder.CoreCodecQueueInputEndOfStream();
events.WaitForInputPacketDoneCount(2, /*stop_on_fail_codec=*/false);
EXPECT_EQ(events.fail_codec_count(), 1u);
EXPECT_EQ(events.input_packet_done_count(), 2u);
EXPECT_EQ(events.output_eos_count(), 0u);
encoder.CoreCodecStopStream();
}
} // namespace