| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #include "chromecast/media/cma/decoder/external_audio_decoder_wrapper.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/containers/heap_array.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/no_destructor.h" |
| #include "base/scoped_native_library.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/time/time.h" |
| #include "chromecast/media/api/decoder_buffer_base.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/audio_sample_types.h" |
| #include "media/base/decoder_buffer.h" |
| |
| namespace chromecast { |
| namespace media { |
| |
| namespace { |
| |
| const char kDefaultExternalDecoderPath[] = "libcast_external_decoder.so"; |
| |
| const char kSupportedConfigFunction[] = |
| "ExternalAudioDecoder_IsSupportedConfig"; |
| const char kCreateFunction[] = "ExternalAudioDecoder_CreateDecoder"; |
| const char kDeleteFunction[] = "ExternalAudioDecoder_DeleteDecoder"; |
| |
| const size_t kMinConversionBufferSize = 256; |
| |
| class ExternalDecoderLib { |
| public: |
| ExternalDecoderLib() : lib_(base::FilePath(kDefaultExternalDecoderPath)) { |
| if (lib_.is_valid()) { |
| supported_config_func_ = reinterpret_cast<IsSupportedConfigFunction>( |
| lib_.GetFunctionPointer(kSupportedConfigFunction)); |
| create_func_ = reinterpret_cast<CreateFunction>( |
| lib_.GetFunctionPointer(kCreateFunction)); |
| delete_func_ = reinterpret_cast<DeleteFunction>( |
| lib_.GetFunctionPointer(kDeleteFunction)); |
| |
| LOG_IF(ERROR, !supported_config_func_) |
| << "Missing function: " << kSupportedConfigFunction; |
| LOG_IF(ERROR, !create_func_) << "Missing function: " << kCreateFunction; |
| LOG_IF(ERROR, !delete_func_) << "Missing function: " << kDeleteFunction; |
| } |
| } |
| |
| ExternalDecoderLib(const ExternalDecoderLib&) = delete; |
| ExternalDecoderLib& operator=(const ExternalDecoderLib&) = delete; |
| |
| ~ExternalDecoderLib() = default; |
| |
| bool IsSupportedConfig(const AudioConfig& config) { |
| if (!supported_config_func_ || !create_func_ || !delete_func_) { |
| return false; |
| } |
| |
| return supported_config_func_(config); |
| } |
| |
| ExternalAudioDecoder* CreateDecoder( |
| ExternalAudioDecoder::Delegate* delegate, |
| const chromecast::media::AudioConfig& config) { |
| if (!create_func_ || !delete_func_) { |
| return nullptr; |
| } |
| |
| return create_func_(delegate, config); |
| } |
| |
| void DeleteDecoder(ExternalAudioDecoder* decoder) { |
| DCHECK(delete_func_); |
| delete_func_(decoder); |
| } |
| |
| private: |
| using IsSupportedConfigFunction = |
| decltype(&ExternalAudioDecoder_IsSupportedConfig); |
| using CreateFunction = decltype(&ExternalAudioDecoder_CreateDecoder); |
| using DeleteFunction = decltype(&ExternalAudioDecoder_DeleteDecoder); |
| |
| base::ScopedNativeLibrary lib_; |
| IsSupportedConfigFunction supported_config_func_ = nullptr; |
| CreateFunction create_func_ = nullptr; |
| DeleteFunction delete_func_ = nullptr; |
| }; |
| |
| ExternalDecoderLib& GetLib() { |
| static base::NoDestructor<ExternalDecoderLib> g_lib; |
| return *g_lib; |
| } |
| |
| AudioConfig BuildOutputConfig(const AudioConfig& input_config, |
| CastAudioDecoder::OutputFormat output_format, |
| ExternalAudioDecoder* decoder) { |
| AudioConfig output_config = input_config; |
| output_config.encryption_scheme = EncryptionScheme::kUnencrypted; |
| output_config.codec = kCodecPCM; |
| output_config.sample_format = |
| (output_format == CastAudioDecoder::kOutputSigned16 |
| ? kSampleFormatS16 |
| : kSampleFormatPlanarF32); |
| output_config.channel_number = decoder->GetNumOutputChannels(); |
| if (output_config.channel_number <= 0) { |
| output_config.channel_number = input_config.channel_number; |
| } |
| return output_config; |
| } |
| |
| } // namespace |
| |
| class ExternalAudioDecoderWrapper::DecodedBuffer : public DecoderBufferBase { |
| public: |
| DecodedBuffer(StreamId stream_id, size_t capacity) |
| : stream_id_(stream_id), |
| capacity_(capacity), |
| data_(base::HeapArray<uint8_t>::Uninit(capacity_)) {} |
| |
| void set_size(size_t size) { |
| DCHECK_LE(size, capacity_); |
| size_ = size; |
| } |
| |
| // DecoderBufferBase implementation: |
| StreamId stream_id() const override { return stream_id_; } |
| int64_t timestamp() const override { return timestamp_.InMicroseconds(); } |
| void set_timestamp(base::TimeDelta timestamp) override { |
| timestamp_ = timestamp; |
| } |
| const uint8_t* data() const override { return data_.data(); } |
| uint8_t* writable_data() const override { |
| return const_cast<uint8_t*>(data_.data()); |
| } |
| size_t data_size() const override { return size_; } |
| const CastDecryptConfig* decrypt_config() const override { return nullptr; } |
| bool end_of_stream() const override { return false; } |
| bool is_key_frame() const override { return false; } |
| |
| private: |
| ~DecodedBuffer() override = default; |
| |
| const StreamId stream_id_; |
| const size_t capacity_; |
| |
| const base::HeapArray<uint8_t> data_; |
| |
| base::TimeDelta timestamp_; |
| size_t size_ = 0; |
| }; |
| |
| // static |
| bool ExternalAudioDecoderWrapper::IsSupportedConfig(const AudioConfig& config) { |
| return GetLib().IsSupportedConfig(config); |
| } |
| |
| ExternalAudioDecoderWrapper::ExternalAudioDecoderWrapper( |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| const AudioConfig& config, |
| CastAudioDecoder::OutputFormat output_format) |
| : task_runner_(std::move(task_runner)), |
| output_format_(output_format), |
| decoder_(GetLib().CreateDecoder(this, config)), |
| output_config_(BuildOutputConfig(config, output_format_, decoder_)) {} |
| |
| ExternalAudioDecoderWrapper::~ExternalAudioDecoderWrapper() { |
| if (decoder_) { |
| GetLib().DeleteDecoder(decoder_); |
| } |
| } |
| |
| const AudioConfig& ExternalAudioDecoderWrapper::GetOutputConfig() const { |
| return output_config_; |
| } |
| |
| void ExternalAudioDecoderWrapper::Decode( |
| scoped_refptr<media::DecoderBufferBase> data, |
| DecodeCallback decode_callback) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| // Post a task, since some callers don't expect a synchronous callback. |
| task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&ExternalAudioDecoderWrapper::DecodeDeferred, |
| weak_factory_.GetWeakPtr(), std::move(data), |
| std::move(decode_callback))); |
| } |
| |
| void ExternalAudioDecoderWrapper::DecodeDeferred( |
| scoped_refptr<media::DecoderBufferBase> data, |
| DecodeCallback decode_callback) { |
| if (data->end_of_stream()) { |
| std::move(decode_callback).Run(kDecodeOk, output_config_, std::move(data)); |
| return; |
| } |
| |
| if (!decoder_ || !decoder_->Decode(*data)) { |
| std::move(decode_callback).Run(kDecodeError, output_config_, nullptr); |
| return; |
| } |
| |
| size_t buffer_count = buffers_.size() - pending_buffer_; |
| scoped_refptr<DecodedBuffer> decoded; |
| if (buffer_count == 0) { |
| decoded = base::MakeRefCounted<DecodedBuffer>(output_config_.id, 0); |
| } else if (buffer_count == 1) { |
| decoded = std::move(buffers_.front()); |
| } else { |
| size_t size = 0; |
| for (size_t i = 0; i < buffer_count; ++i) { |
| size += buffers_[i]->data_size(); |
| } |
| |
| decoded = base::MakeRefCounted<DecodedBuffer>(output_config_.id, size); |
| decoded->set_size(size); |
| |
| const size_t frame_size = sizeof(float) * output_config_.channel_number; |
| size_t total_frames = size / frame_size; |
| for (int c = 0; c < output_config_.channel_number; ++c) { |
| uint8_t* dest = |
| decoded->writable_data() + c * total_frames * sizeof(float); |
| for (size_t i = 0; i < buffer_count; ++i) { |
| size_t frames = buffers_[i]->data_size() / frame_size; |
| void* src = buffers_[i]->writable_data() + c * frames * sizeof(float); |
| memcpy(dest, src, frames * sizeof(float)); |
| dest += frames * sizeof(float); |
| } |
| } |
| } |
| |
| buffers_.erase(buffers_.begin(), buffers_.begin() + buffer_count); |
| |
| if (output_format_ == CastAudioDecoder::kOutputSigned16) { |
| ConvertToS16(decoded.get()); |
| } |
| |
| decoded->set_timestamp(base::Microseconds(data->timestamp())); |
| std::move(decode_callback).Run(kDecodeOk, output_config_, std::move(decoded)); |
| } |
| |
| void ExternalAudioDecoderWrapper::ConvertToS16(DecodedBuffer* buffer) { |
| const int channels = output_config_.channel_number; |
| const size_t frame_size = sizeof(float) * channels; |
| const size_t frames = buffer->data_size() / frame_size; |
| |
| if (!conversion_buffer_ || |
| conversion_buffer_->frames() < static_cast<int>(frames) || |
| conversion_buffer_->channels() != channels) { |
| conversion_buffer_ = ::media::AudioBus::Create( |
| channels, std::max(frames * 2, kMinConversionBufferSize)); |
| } |
| |
| const float* src = reinterpret_cast<const float*>(buffer->data()); |
| for (int c = 0; c < channels; ++c) { |
| std::copy_n(src + c * frames, frames, conversion_buffer_->channel(c)); |
| } |
| |
| int16_t* dest = reinterpret_cast<int16_t*>(buffer->writable_data()); |
| conversion_buffer_ |
| ->ToInterleavedPartial<::media::SignedInt16SampleTypeTraits>(0, frames, |
| dest); |
| |
| buffer->set_size(frames * channels * sizeof(int16_t)); |
| } |
| |
| void* ExternalAudioDecoderWrapper::AllocateBuffer(size_t bytes) { |
| auto buffer = base::MakeRefCounted<DecodedBuffer>(output_config_.id, bytes); |
| void* ptr = buffer->writable_data(); |
| buffers_.push_back(std::move(buffer)); |
| pending_buffer_ = true; |
| return ptr; |
| } |
| |
| void ExternalAudioDecoderWrapper::OnDecodedBuffer(size_t decoded_size_bytes, |
| const AudioConfig& config) { |
| DCHECK(!buffers_.empty()); |
| size_t frame_size = sizeof(float) * config.channel_number; |
| DCHECK_EQ(decoded_size_bytes % frame_size, 0u); |
| |
| buffers_.back()->set_size(decoded_size_bytes); |
| output_config_.channel_number = config.channel_number; |
| output_config_.samples_per_second = config.samples_per_second; |
| pending_buffer_ = false; |
| } |
| |
| } // namespace media |
| } // namespace chromecast |