blob: 5f0d2fc663130d399bb9def14769fe9b0510dd09 [file] [log] [blame]
// Copyright 2020 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 "src/media/sounds/soundplayer/opus_decoder.h"
#include <endian.h>
#include <lib/syslog/cpp/macros.h>
#include <memory>
#include "third_party/opus/include/opus.h"
namespace soundplayer {
namespace {
// Creates a 64-bit value from 8 bytes. Used for Opus signature value.
static inline constexpr uint64_t make_eightcc(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e,
uint8_t f, uint8_t g, uint8_t h) {
return htole64((static_cast<uint64_t>(h) << 56) | (static_cast<uint64_t>(g) << 48) |
(static_cast<uint64_t>(f) << 40) | (static_cast<uint64_t>(e) << 32) |
(static_cast<uint64_t>(d) << 24) | (static_cast<uint64_t>(c) << 16) |
(static_cast<uint64_t>(b) << 8) | static_cast<uint64_t>(a));
}
static constexpr uint64_t kIdHeaderSignature = make_eightcc('O', 'p', 'u', 's', 'H', 'e', 'a', 'd');
static constexpr uint64_t kCommentHeaderSignature =
make_eightcc('O', 'p', 'u', 's', 'T', 'a', 'g', 's');
static constexpr uint8_t kSupportedVersion = 1;
static constexpr uint8_t kSupportedMappingFamily = 0;
static constexpr uint32_t kOutputFramesPerSecond = 48000;
static constexpr size_t kOutputBufferMaxFrameCount = 5760; // 120ms at 48k
struct IdHeader {
uint64_t signature_; // 'OpusHead' (kIdHeaderSignature)
uint8_t version_;
uint8_t channel_count_;
uint16_t preskip_;
uint32_t input_sample_rate_;
uint16_t output_gain_;
uint8_t mapping_family_;
} __PACKED;
struct CommentHeader {
uint64_t signature_; // 'OpusTags' (kCommentHeaderSignature)
// Variable stuff follows. Not used.
} __PACKED;
} // namespace
// static
bool OpusDecoder::CheckHeaderPacket(const uint8_t* data, size_t size) {
if (size < sizeof(IdHeader)) {
// Header too small.
return false;
}
auto& header = *reinterpret_cast<const IdHeader*>(data);
if (header.signature_ != kIdHeaderSignature) {
// Signature not found.
return false;
}
if (header.version_ != kSupportedVersion) {
// Unsupported version.
return false;
}
if (header.channel_count_ != 1 && header.channel_count_ != 2) {
// Unsupported channel count.
return false;
}
if (header.mapping_family_ != kSupportedMappingFamily) {
// Unsupported mapping family.
return false;
}
return true;
}
OpusDecoder::OpusDecoder() {}
OpusDecoder::~OpusDecoder() {}
bool OpusDecoder::ProcessPacket(const uint8_t* data, size_t size, bool first, bool last) {
if (first) {
second_packet_processed_ = false;
total_frame_count_ = 0;
output_buffers_.clear();
return ProcessIdHeader(data, size);
}
if (!second_packet_processed_) {
second_packet_processed_ = true;
return ProcessCommentHeader(data, size);
}
FX_DCHECK(decoder_);
auto output_buffer = std::make_unique<int16_t[]>(kOutputBufferMaxFrameCount * channels_);
int decoded_frame_count_or_error =
opus_decode(decoder_.get(), data, size, output_buffer.get(), kOutputBufferMaxFrameCount, 0);
if (decoded_frame_count_or_error < 0) {
// Decode failed.
FX_LOGS(WARNING) << "opus_decode failed, error " << decoded_frame_count_or_error;
return false;
}
HandleOutputBuffer(std::move(output_buffer), static_cast<uint32_t>(decoded_frame_count_or_error));
if (last) {
return HandleEndOfStream();
}
return true;
}
bool OpusDecoder::ProcessIdHeader(const uint8_t* data, size_t size) {
// |CheckHeaderPacket| approved the header previously, so we can just DCHECK what was checked
// there.
FX_DCHECK(size >= sizeof(IdHeader));
auto& header = *reinterpret_cast<const IdHeader*>(data);
FX_DCHECK(header.signature_ == kIdHeaderSignature);
FX_DCHECK(header.version_ == kSupportedVersion);
FX_DCHECK(header.channel_count_ == 1 || header.channel_count_ == 2);
FX_DCHECK(header.mapping_family_ == kSupportedMappingFamily);
channels_ = header.channel_count_;
preskip_ = header.preskip_;
input_frames_per_second_ = header.input_sample_rate_;
int error;
decoder_ = std::unique_ptr<::OpusDecoder, DecoderDeleter>(
opus_decoder_create(kOutputFramesPerSecond, channels_, &error));
if (!decoder_) {
FX_LOGS(ERROR) << "opus_decoder_create failed, error " << error;
return false;
}
return true;
}
bool OpusDecoder::ProcessCommentHeader(const uint8_t* data, size_t size) {
if (size < sizeof(CommentHeader)) {
// Header too small.
return false;
}
auto& header = *reinterpret_cast<const CommentHeader*>(data);
if (header.signature_ != kCommentHeaderSignature) {
// Signature not found.
return false;
}
// If metadata from the comment header is needed, parse it here.
return true;
}
void OpusDecoder::HandleOutputBuffer(std::unique_ptr<int16_t[]> buffer, uint32_t frame_count) {
output_buffers_.emplace_back(std::move(buffer), frame_count);
total_frame_count_ += frame_count;
}
bool OpusDecoder::HandleEndOfStream() {
size_t frame_size = sizeof(int16_t) * channels_;
size_t vmo_size = (total_frame_count_ - preskip_) * frame_size;
zx_status_t status = zx::vmo::create(vmo_size, 0, &vmo_);
if (status != ZX_OK) {
FX_PLOGS(WARNING, status) << "zx::vmo::create failed";
return false;
}
uint16_t preskip_remaining = preskip_;
uint64_t offset = 0;
for (auto& output_buffer : output_buffers_) {
if (preskip_remaining >= output_buffer.frame_count_) {
preskip_remaining -= output_buffer.frame_count_;
continue;
}
size_t size = (output_buffer.frame_count_ - preskip_remaining) * frame_size;
status = vmo_.write(output_buffer.samples_.get() + preskip_remaining * channels_, offset, size);
if (status != ZX_OK) {
FX_PLOGS(WARNING, status) << "zx::vmo::write failed";
return false;
}
offset += size;
preskip_remaining = 0;
}
output_buffers_.clear();
return true;
}
Sound OpusDecoder::TakeSound() {
if (!vmo_) {
return Sound();
}
return Sound(
std::move(vmo_), (total_frame_count_ - preskip_) * sizeof(int16_t) * channels_,
fuchsia::media::AudioStreamType{.sample_format = fuchsia::media::AudioSampleFormat::SIGNED_16,
.channels = channels_,
.frames_per_second = kOutputFramesPerSecond});
}
} // namespace soundplayer