blob: 298b0c94b26bcd8173b63787adedd18830b45b04 [file] [log] [blame]
// Copyright 2019 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 "codec_adapter_sbc_decoder.h"
#include <lib/media/codec_impl/codec_buffer.h>
#include <lib/trace/event.h>
#include <iomanip>
#include <limits>
#include "fuchsia/media/cpp/fidl.h"
#include "lib/media/codec_impl/codec_port.h"
#include "oi_codec_sbc.h"
namespace {
constexpr char kSbcMimeType[] = "audio/sbc";
constexpr char kMsbcMimeType[] = "audio/msbc";
constexpr char kPcmMimeType[] = "audio/pcm";
constexpr uint8_t kPcmBitsPerSample = 16;
constexpr size_t kMaxInputFrames = 64;
} // namespace
CodecAdapterSbcDecoder::CodecAdapterSbcDecoder(std::mutex& lock,
CodecAdapterEvents* codec_adapter_events)
: CodecAdapterSW(lock, codec_adapter_events) {}
CodecAdapterSbcDecoder::~CodecAdapterSbcDecoder() = default;
void CodecAdapterSbcDecoder::ProcessInputLoop() {
std::optional<CodecInputItem> maybe_input_item;
while ((maybe_input_item = input_queue_.WaitForElement())) {
CodecInputItem input_item = std::move(maybe_input_item.value());
if (input_item.is_format_details()) {
if (context_) {
events_->onCoreCodecFailCodec("Midstream input format change is not supported.");
return;
}
if (CreateContext(std::move(input_item.format_details())) != kOk) {
// Creation failed; a failure was reported through `events_`.
return;
}
events_->onCoreCodecMidStreamOutputConstraintsChange(
/*output_re_config_required=*/true);
} else if (input_item.is_end_of_stream()) {
ZX_DEBUG_ASSERT(context_);
if (DecodeInput(nullptr) == kShouldTerminate) {
events_->onCoreCodecFailCodec("Failed to stop stream");
return;
}
} else if (input_item.is_packet()) {
ZX_DEBUG_ASSERT(context_);
if (DecodeInput(input_item.packet()) == kShouldTerminate) {
events_->onCoreCodecFailCodec("Failed to decode packet");
return;
}
}
}
}
void CodecAdapterSbcDecoder::CleanUpAfterStream() { context_ = std::nullopt; }
std::pair<fuchsia::media::FormatDetails, size_t> CodecAdapterSbcDecoder::OutputFormatDetails() {
FX_DCHECK(context_);
fuchsia::media::AudioUncompressedFormat uncompressed;
uncompressed.set_pcm(context_->output_format);
fuchsia::media::AudioFormat audio_format;
audio_format.set_uncompressed(std::move(uncompressed));
fuchsia::media::FormatDetails format_details;
format_details.set_mime_type(kPcmMimeType);
format_details.mutable_domain()->set_audio(std::move(audio_format));
return {std::move(format_details), context_->max_pcm_chunk_size()};
}
fuchsia_sysmem2::BufferCollectionConstraints
CodecAdapterSbcDecoder::CoreCodecGetBufferCollectionConstraints2(
CodecPort port, const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
const fuchsia::media::StreamBufferPartialSettings& partial_settings) {
std::lock_guard<std::mutex> lock(lock_);
fuchsia_sysmem2::BufferCollectionConstraints result;
// The CodecImpl won't hand us the sysmem token, so we shouldn't expect to
// have the token here.
ZX_DEBUG_ASSERT(!partial_settings.has_sysmem_token());
// TODO(https://fxbug.dev/42084949): plumb/permit range of buffer count from further down,
// instead of single number frame_count, and set this to the actual
// stream-required # of reference frames + # that can concurrently decode.
// Packets and buffers are not the same thing, and we should permit the # of
// packets to be >= the # of buffers. We shouldn't be
// allocating buffers on behalf of the client here, but until we plumb the
// range of frame_count and are more flexible on # of allocated buffers, we
// have to make sure there are at least as many buffers as packets. We
// categorize the buffers as for camping and for slack. This should change to
// be just the buffers needed for camping and maybe 1 for shared slack. If
// the client wants more buffers the client can demand buffers in its own
// fuchsia::sysmem::BufferCollection::SetConstraints().
if (port == kOutputPort) {
result.min_buffer_count_for_camping() = kMinOutputBufferCountForCamping;
} else {
result.min_buffer_count_for_camping() = kMinInputBufferCountForCamping;
}
ZX_DEBUG_ASSERT(!result.min_buffer_count_for_dedicated_slack().has_value());
ZX_DEBUG_ASSERT(!result.min_buffer_count_for_shared_slack().has_value());
uint32_t per_packet_buffer_bytes_min;
uint32_t per_packet_buffer_bytes_max;
if (port == kInputPort) {
per_packet_buffer_bytes_min = kMaxInputFrames * SBC_MAX_FRAME_LEN;
per_packet_buffer_bytes_max = kMaxInputFrames * SBC_MAX_FRAME_LEN;
} else {
ZX_ASSERT(context_.has_value());
ZX_DEBUG_ASSERT(port == kOutputPort);
per_packet_buffer_bytes_min = context_->max_pcm_chunk_size();
// At least for now, don't cap the per-packet buffer size for output.
per_packet_buffer_bytes_max = 0xFFFFFFFF;
}
auto& bmc = result.buffer_memory_constraints().emplace();
bmc.min_size_bytes() = per_packet_buffer_bytes_min;
bmc.max_size_bytes() = per_packet_buffer_bytes_max;
// These are all false because SW encode.
bmc.physically_contiguous_required() = false;
bmc.secure_required() = false;
ZX_DEBUG_ASSERT(!result.image_format_constraints().has_value());
// We don't have to fill out usage - CodecImpl takes care of that.
ZX_DEBUG_ASSERT(!result.usage().has_value());
return result;
}
void CodecAdapterSbcDecoder::CoreCodecStopStream() {
async::PostTask(input_processing_loop_.dispatcher(), [this] {
if (output_buffer_) {
// If we have an output buffer pending but not sent, return it to the pool. CodecAdapterSW
// expects all buffers returned after stream is stopped.
auto base = output_buffer_->base();
output_buffer_pool_.FreeBuffer(base);
output_buffer_ = nullptr;
}
});
CodecAdapterSW::CoreCodecStopStream();
}
fuchsia::media::PcmFormat CodecAdapterSbcDecoder::DecodeCodecInfo(
const std::vector<uint8_t>& oob_bytes) {
fuchsia::media::PcmFormat out;
SbcCodecInfo codec_info;
FX_DCHECK(oob_bytes.size() == sizeof(SbcCodecInfo));
// SBC codec info assumed to be msbf order
memcpy(&codec_info, oob_bytes.data(), sizeof(SbcCodecInfo));
out.bits_per_sample = kPcmBitsPerSample;
switch (codec_info.channel_mode) {
case kSbcChannelModeMono:
out.channel_map = {fuchsia::media::AudioChannelId::LF};
break;
default:
out.channel_map = {fuchsia::media::AudioChannelId::LF, fuchsia::media::AudioChannelId::RF};
break;
}
switch (codec_info.sampling_frequency) {
case kSbcSamplingFrequency16000Hz:
out.frames_per_second = 16000;
break;
case kSbcSamplingFrequency32000Hz:
out.frames_per_second = 32000;
break;
case kSbcSamplingFrequency44100Hz:
out.frames_per_second = 44100;
break;
case kSbcSamplingFrequency48000Hz:
out.frames_per_second = 48000;
break;
default:
FX_LOGS(WARNING) << "invalid frequency";
break;
}
return out;
}
CodecAdapterSbcDecoder::InputLoopStatus CodecAdapterSbcDecoder::CreateContext(
const fuchsia::media::FormatDetails& format_details) {
if (!format_details.has_mime_type() ||
(format_details.mime_type() != kSbcMimeType && format_details.mime_type() != kMsbcMimeType)) {
events_->onCoreCodecFailCodec("SBC Decoder received mime type that was not sbc or msbc audio.");
return kShouldTerminate;
}
if (!format_details.has_oob_bytes() ||
format_details.oob_bytes().size() != sizeof(SbcCodecInfo)) {
events_->onCoreCodecFailCodec(
"SBC Decoder received oob info input that was not for sbc audio.");
return kShouldTerminate;
}
auto output_pcm_format = DecodeCodecInfo(format_details.oob_bytes());
uint8_t channels = static_cast<uint8_t>(output_pcm_format.channel_map.size());
context_ = {.output_format = output_pcm_format};
OI_STATUS status = OI_CODEC_SBC_DecoderReset(&context_->context, context_->context_data,
sizeof(context_->context_data), channels,
/*pcmStride=*/channels, /*enhanced=*/false);
if (!OI_SUCCESS(status)) {
events_->onCoreCodecFailCodec("Failed to reset SBC decoder");
return kShouldTerminate;
}
if (format_details.mime_type() == kMsbcMimeType) {
status = OI_CODEC_SBC_DecoderConfigureMSbc(&context_->context);
if (!OI_SUCCESS(status)) {
events_->onCoreCodecFailCodec("Failed to set mSBC on decoder");
return kShouldTerminate;
}
}
return kOk;
}
CodecAdapterSbcDecoder::InputLoopStatus CodecAdapterSbcDecoder::DecodeInput(
CodecPacket* input_packet) {
FX_DCHECK(context_);
if (!input_packet) {
events_->onCoreCodecOutputEndOfStream(/*error_detected_before=*/false);
return kOk;
}
auto return_to_client =
fit::defer([this, input_packet]() { events_->onCoreCodecInputPacketDone(input_packet); });
uint32_t bytes_left = input_packet->valid_length_bytes();
uint8_t* input_data = input_packet->buffer()->base() + input_packet->start_offset();
while (bytes_left > 0) {
uint8_t* output = CurrentOutputBlock();
if (output == nullptr) {
// The stream is ending.
return kShouldTerminate;
}
FX_DCHECK(output_buffer_);
FX_DCHECK(output_packet_);
if (output_buffer_->size() - output_offset_ > std::numeric_limits<uint32_t>::max()) {
FX_LOGS(WARNING) << "Could not represent output_bytes as uint32_t";
break;
}
uint32_t output_bytes = static_cast<uint32_t>(output_buffer_->size() - output_offset_);
OI_STATUS status =
OI_CODEC_SBC_DecodeFrame(&context_->context, const_cast<const OI_BYTE**>(&input_data),
&bytes_left, reinterpret_cast<int16_t*>(output), &output_bytes);
if (!OI_SUCCESS(status)) {
FX_LOGS(WARNING) << "decode failure " << status << " bytes left " << bytes_left;
break;
}
// Must be called for each frame to send when output_buffer is low
// output_bytes is the same on every call for a given stream (set from DecodeFrame)
QueueAndSend(output_bytes);
}
SendQueuedOutput();
return kOk;
}
uint8_t* CodecAdapterSbcDecoder::CurrentOutputBlock() {
if (output_packet_ == nullptr) {
std::optional<CodecPacket*> maybe_output_packet = free_output_packets_.WaitForElement();
if (!maybe_output_packet) {
// The stream is ending.
return nullptr;
}
FX_DCHECK(*maybe_output_packet != nullptr);
output_packet_ = *maybe_output_packet;
}
if (!output_buffer_) {
output_buffer_ = output_buffer_pool_.AllocateBuffer();
output_offset_ = 0;
}
if (!output_buffer_) {
return nullptr;
}
FX_DCHECK(output_offset_ < output_buffer_->size());
// Caller must set `output_buffer` to `nullptr` when space is insufficient.
uint8_t* output = output_buffer_->base() + output_offset_;
return output;
}
void CodecAdapterSbcDecoder::SendQueuedOutput() {
if (!output_buffer_ || !output_packet_ || !output_offset_) {
return;
}
TRACE_INSTANT("codec_runner", "Media:PacketSent", TRACE_SCOPE_THREAD);
if (output_offset_ > std::numeric_limits<uint32_t>::max()) {
events_->onCoreCodecFailCodec("Could not represent output_offset_ as uint32_t");
return;
}
output_packet_->SetBuffer(output_buffer_);
output_packet_->SetValidLengthBytes(static_cast<uint32_t>(output_offset_));
output_packet_->SetStartOffset(0);
{
fit::closure free_buffer = [this, base = output_packet_->buffer()->base()] {
output_buffer_pool_.FreeBuffer(base);
};
std::lock_guard<std::mutex> lock(lock_);
in_use_by_client_[output_packet_] = fit::defer(std::move(free_buffer));
}
events_->onCoreCodecOutputPacket(output_packet_,
/*error_detected_before=*/false,
/*error_detected_during=*/false);
output_packet_ = nullptr;
output_buffer_ = nullptr;
output_offset_ = 0;
}
void CodecAdapterSbcDecoder::QueueAndSend(size_t bytes_written) {
FX_DCHECK(output_offset_ + bytes_written <= output_buffer_->size());
output_offset_ += bytes_written;
// Presume that the output buffer will remain the same size for the next frame,
// and that each frame decodes to the same number of output bytes, both of which are true for SBC.
// Send output packet if the next decoded frame will not fit in this buffer
if ((output_buffer_->size() - output_offset_) < bytes_written) {
SendQueuedOutput();
}
}
void CodecAdapterSbcDecoder::CoreCodecSetBufferCollectionInfo(
CodecPort port, const fuchsia_sysmem2::BufferCollectionInfo& buffer_collection_info) {
if (port == kInputPort) {
ZX_DEBUG_ASSERT(buffer_collection_info.buffers()->size() >= kMinInputBufferCountForCamping);
} else {
ZX_DEBUG_ASSERT(buffer_collection_info.buffers()->size() >= kMinOutputBufferCountForCamping);
}
}