#include "stream/stream.h" #include #include #include #include "adm/adm_metadata.h" #include "foundation/status.h" #include "hrtf/jochrtf.h" #include "joc_bitstream/joc_parser.h" #include "joc_core/objects16.h" namespace joc::stream { namespace { constexpr std::size_t kFrameSamples = JOC_FRAME_SAMPLES; constexpr std::size_t kBedChannels = 6; constexpr int kCoreChannels[5] = {0, 1, 2, 4, 5}; constexpr int kLfeChannel = 3; } // namespace Stream::~Stream() { if (rebuilder_ != nullptr) { ejoc_renderer_destroy(rebuilder_); rebuilder_ = nullptr; } if (speaker_.handle != nullptr) { ejoc_speaker_renderer_destroy(speaker_.handle); speaker_.handle = nullptr; } } void Stream::reset_state() { reader_ = eac3::FrameReader(); metadata_.clear(); bed_pending_.clear(); objects16_.clear(); output_.clear(); read_offset_ = 0; info_ = Info(); info_.output_channels = output_channels_; if (rebuilder_ != nullptr) { ejoc_renderer_reset(rebuilder_); } if (speaker_.handle != nullptr) { ejoc_speaker_renderer_reset(speaker_.handle); speaker_.state.reset(); speaker_.output.clear(); speaker_.last_had_payload = false; } if (binaural_ready_) { binaural_.reset(); } } Status Stream::create(const Config& config) { if (rebuilder_ != nullptr) { return Status::fail(JOC_ERR_STATE, stage::kRender, "stream already created"); } config_ = config; if (config_.input > JOC_STREAM_IN_PCM_OBJECTS16 || config_.output > JOC_STREAM_OUT_BINAURAL) { return Status::fail(JOC_ERR_INVALID_CONFIG, stage::kRender, "unknown stream kind"); } if (config_.input == JOC_STREAM_IN_PCM_OBJECTS16 && config_.output == JOC_STREAM_OUT_PCM_OBJECTS16) { return Status::fail(JOC_ERR_INVALID_CONFIG, stage::kRender, "objects16 input with objects16 output would do nothing"); } gain_ = static_cast(std::pow(10.0, config_.gain_db / 20.0)); if (config_.input == JOC_STREAM_IN_EAC3) { rebuilder_ = ejoc_renderer_create(); if (rebuilder_ == nullptr) { return Status::fail(JOC_ERR_OUT_OF_MEMORY, stage::kDsp, "cannot create the JOC kernel"); } if (config_.native_threads != 0u) { ejoc_renderer_set_threads(rebuilder_, config_.native_threads); } } if (config_.output == JOC_STREAM_OUT_SPEAKER) { speaker_enabled_ = true; if (!speaker::layout_by_name(config_.layout.c_str(), &speaker_.layout)) { return Status::fail(JOC_ERR_LAYOUT_UNSUPPORTED, stage::kRender, "unknown speaker layout: " + config_.layout); } speaker_.metadata_offset = config_.metadata_offset; speaker_.handle = ejoc_speaker_renderer_create(speaker_.layout.speaker_bitfield); if (speaker_.handle == nullptr) { return Status::fail(JOC_ERR_RENDER_FAILED, stage::kRender, "cannot create the speaker renderer"); } output_channels_ = speaker_.layout.channel_count; } else if (config_.output == JOC_STREAM_OUT_BINAURAL) { binaural_enabled_ = true; hrtf::Field field; hrtf::Kernels kernels; Status status = hrtf::load_jochrtf(config_.hrtf_path, &field); if (status.ok()) { status = config_.kernels_path.empty() ? (kernels = hrtf::builtin_kernels(), Status::success()) : hrtf::load_kernels(config_.kernels_path, &kernels); } binaural::Profile profile = binaural::Profile::Mid; if (status.ok() && config_.binaural_mode == JOC_BINAURAL_NEAR) { profile = binaural::Profile::Near; } else if (status.ok() && config_.binaural_mode == JOC_BINAURAL_FAR) { profile = binaural::Profile::Far; } if (status.ok()) { status = binaural_.open(field, kernels, profile); } if (!status.ok()) { return status; } binaural_ready_ = true; output_channels_ = 2; } else { output_channels_ = JOC_OUTPUT_CHANNELS; } reset_state(); return Status::success(); } Status Stream::push_eac3(const std::uint8_t* data, std::size_t size, std::size_t* consumed) { if (rebuilder_ == nullptr) { return Status::fail(JOC_ERR_STATE, stage::kEmdf, "stream is not an E-AC-3 input"); } if (consumed != nullptr) { *consumed = size; } if (data != nullptr && size != 0u) { reader_.push(data, size); info_.bytes_in += size; } for (;;) { eac3::Frame frame; const eac3::FrameReader::Next state = reader_.next(&frame); if (state == eac3::FrameReader::Next::End) { break; } if (state == eac3::FrameReader::Next::Fail) { return Status::fail(reader_.error(), stage::kEac3, reader_.error_message()); } FrameMetadata entry; emdf::Container container; const Status parsed = joc::parse_eac3_frame(frame.data, frame.size, &entry.params, &container, nullptr); if (!parsed.ok()) { return parsed; } if (const emdf::Payload* payload = container.find(emdf::kIdOamd)) { std::vector bytes; const Status extracted = emdf::extract_payload_bytes(frame.data, frame.size, *payload, &bytes); if (!extracted.ok()) { return extracted; } const Status oamd = oamd::parse_id11(bytes.data(), bytes.size(), &entry.update); if (!oamd.ok()) { return oamd; } entry.has_update = true; entry.outer_offset = static_cast(payload->sample_offset); } metadata_.push_back(entry); } return process_ready_frames(); } Status Stream::push_bed(const float* interleaved6, std::size_t samples, std::size_t* consumed) { if (rebuilder_ == nullptr) { return Status::fail(JOC_ERR_STATE, stage::kDsp, "stream is not an E-AC-3 input"); } if (consumed != nullptr) { *consumed = samples; } if (interleaved6 != nullptr && samples != 0u) { bed_pending_.insert(bed_pending_.end(), interleaved6, interleaved6 + samples * kBedChannels); } return process_ready_frames(); } Status Stream::push_objects16(const float* planar16, std::size_t samples, std::size_t* consumed) { if (consumed != nullptr) { *consumed = samples; } if (planar16 == nullptr || samples == 0u) { return Status::success(); } // Rendered immediately: the host has already done the JOC rebuild. for (std::size_t offset = 0; offset < samples; offset += kFrameSamples) { const std::size_t count = std::min(kFrameSamples, samples - offset); std::vector frame(static_cast(JOC_OUTPUT_CHANNELS) * kFrameSamples, 0.0f); for (std::size_t channel = 0; channel < JOC_OUTPUT_CHANNELS; ++channel) { std::memcpy(frame.data() + channel * kFrameSamples, planar16 + channel * samples + offset, count * sizeof(float)); } ++info_.frames_in; info_.samples_in += count; const Status rendered = render_objects16(frame); if (!rendered.ok()) { return rendered; } if (count != kFrameSamples) { break; // a partial frame is dropped; the host should push whole frames } } return Status::success(); } Status Stream::process_ready_frames() { while (bed_pending_.size() / kBedChannels >= kFrameSamples && !metadata_.empty()) { const FrameMetadata entry = metadata_.front(); metadata_.pop_front(); std::vector bed5(static_cast(JOC_CORE_CHANNELS) * kFrameSamples, 0.0f); std::vector lfe(kFrameSamples, 0.0f); for (std::size_t sample = 0; sample < kFrameSamples; ++sample) { for (std::size_t channel = 0; channel < JOC_CORE_CHANNELS; ++channel) { bed5[channel * kFrameSamples + sample] = bed_pending_[sample * kBedChannels + kCoreChannels[channel]]; } lfe[sample] = bed_pending_[sample * kBedChannels + kLfeChannel]; } bed_pending_.erase(bed_pending_.begin(), bed_pending_.begin() + static_cast(kFrameSamples * kBedChannels)); std::string error; const Status rebuilt = joc::rebuild_objects16(rebuilder_, entry.params, bed5.data(), lfe.data(), gain_, &objects16_, &error); if (!rebuilt.ok()) { return Status::fail(rebuilt.code(), stage::kDsp, error); } pending_metadata_ = entry; const Status rendered = render_objects16(objects16_); if (!rendered.ok()) { return rendered; } ++info_.frames_in; info_.samples_in += kFrameSamples; } return Status::success(); } Status Stream::render_objects16(const std::vector& objects16) { if (config_.output == JOC_STREAM_OUT_PCM_OBJECTS16) { output_.insert(output_.end(), objects16.begin(), objects16.end()); info_.frames_out++; info_.samples_out += kFrameSamples; return Status::success(); } if (speaker_enabled_) { std::string error; const Status stepped = speaker::step(&speaker_, objects16, pending_metadata_.has_update ? &pending_metadata_.update : nullptr, &error); if (!stepped.ok()) { return Status::fail(stepped.code(), stage::kRender, error); } for (const double value : speaker_.output) { output_.push_back(static_cast(value)); } info_.frames_out++; info_.samples_out += kFrameSamples; return Status::success(); } const Status submitted = binaural_.submit_frame(objects16.data(), pending_metadata_.has_update ? &pending_metadata_.update : nullptr, static_cast(info_.frames_out), pending_metadata_.outer_offset, static_cast(config_.object_delay_samples)); if (!submitted.ok()) { return submitted; } std::vector produced; binaural_.take_output(&produced); for (const double value : produced) { output_.push_back(static_cast(value)); } info_.frames_out++; info_.samples_out += produced.size() / 2u; return Status::success(); } Status Stream::pull(float* destination, std::size_t capacity_samples, std::size_t* produced) { if (produced != nullptr) { *produced = 0; } if (destination == nullptr || produced == nullptr) { return Status::fail(JOC_ERR_INVALID_ARGUMENT, stage::kOutput, "null pull buffer"); } const std::size_t available = buffered_samples(); const std::size_t count = std::min(capacity_samples, available); if (count != 0u) { std::memcpy(destination, output_.data() + read_offset_, count * output_channels_ * sizeof(float)); read_offset_ += count * output_channels_; if (read_offset_ == output_.size()) { output_.clear(); read_offset_ = 0; } else if (read_offset_ > (1u << 20)) { output_.erase(output_.begin(), output_.begin() + static_cast(read_offset_)); read_offset_ = 0; } } *produced = static_cast(count); return Status::success(); } Status Stream::flush() { if (binaural_ready_) { std::vector tail; const Status drained = binaural_.finish(binaural_.finish_capacity(config_.tail_seconds), &tail); if (!drained.ok()) { return drained; } for (const double value : tail) { output_.push_back(static_cast(value)); } info_.samples_out += tail.size() / 2u; } info_.ended = 1; return Status::success(); } Status Stream::reset() { if (rebuilder_ == nullptr && !speaker_enabled_ && !binaural_ready_ && config_.input != JOC_STREAM_IN_PCM_OBJECTS16) { return Status::fail(JOC_ERR_STATE, stage::kRender, "stream is not created"); } reset_state(); return Status::success(); } } // namespace joc::stream