| // Copyright 2017 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "starboard/shared/starboard/player/decoded_audio_internal.h" |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <utility> |
| |
| #include "starboard/common/log.h" |
| #include "starboard/common/media.h" |
| #include "starboard/memory.h" |
| #include "starboard/shared/starboard/media/media_util.h" |
| |
| namespace starboard { |
| namespace shared { |
| namespace starboard { |
| namespace player { |
| |
| namespace { |
| |
| using ::starboard::shared::starboard::media::AudioDurationToFrames; |
| using ::starboard::shared::starboard::media::AudioFramesToDuration; |
| using ::starboard::shared::starboard::media::GetBytesPerSample; |
| |
| void ConvertSample(const int16_t* source, float* destination) { |
| *destination = static_cast<float>(*source) / 32768.f; |
| } |
| |
| void ConvertSample(const float* source, int16_t* destination) { |
| float sample = std::max(*source, -1.f); |
| sample = std::min(sample, 1.f); |
| *destination = static_cast<int16_t>(sample * 32767.f); |
| } |
| |
| } // namespace |
| |
| DecodedAudio::DecodedAudio() |
| : channels_(0), |
| sample_type_(kSbMediaAudioSampleTypeInt16Deprecated), |
| storage_type_(kSbMediaAudioFrameStorageTypeInterleaved), |
| timestamp_(0), |
| offset_in_bytes_(0), |
| size_in_bytes_(0) {} |
| |
| DecodedAudio::DecodedAudio(int channels, |
| SbMediaAudioSampleType sample_type, |
| SbMediaAudioFrameStorageType storage_type, |
| SbTime timestamp, |
| int size_in_bytes) |
| : channels_(channels), |
| sample_type_(sample_type), |
| storage_type_(storage_type), |
| timestamp_(timestamp), |
| storage_(size_in_bytes), |
| offset_in_bytes_(0), |
| size_in_bytes_(size_in_bytes) { |
| SB_DCHECK(channels_ > 0); |
| SB_DCHECK(size_in_bytes_ >= 0); |
| // TODO(b/275199195): Enable the SB_DCHECK below. |
| // SB_DCHECK(size_in_bytes_ % (GetBytesPerSample(sample_type_) * channels_) == |
| // 0); |
| } |
| |
| int DecodedAudio::frames() const { |
| int bytes_per_sample = GetBytesPerSample(sample_type_); |
| SB_DCHECK(size_in_bytes_ % (bytes_per_sample * channels_) == 0); |
| return static_cast<int>(size_in_bytes_ / bytes_per_sample / channels_); |
| } |
| |
| bool DecodedAudio::IsFormat(SbMediaAudioSampleType sample_type, |
| SbMediaAudioFrameStorageType storage_type) const { |
| return sample_type_ == sample_type && storage_type_ == storage_type; |
| } |
| |
| void DecodedAudio::ShrinkTo(int new_size_in_bytes) { |
| SB_DCHECK(new_size_in_bytes <= size_in_bytes_); |
| size_in_bytes_ = new_size_in_bytes; |
| } |
| |
| void DecodedAudio::AdjustForSeekTime(int sample_rate, SbTime seeking_to_time) { |
| SB_DCHECK(!is_end_of_stream()); |
| SB_DCHECK(sample_rate != 0); |
| |
| int frames_to_skip = |
| media::AudioDurationToFrames(seeking_to_time - timestamp(), sample_rate); |
| |
| if (sample_rate == 0 || frames_to_skip < 0 || frames_to_skip >= frames()) { |
| SB_LOG(WARNING) << "AdjustForSeekTime failed for seeking_to_time: " |
| << seeking_to_time << ", sample_rate: " << sample_rate |
| << ", timestamp: " << timestamp() << ", and there are " |
| << frames() << " frames in the DecodedAudio object."; |
| return; |
| } |
| |
| const auto bytes_per_sample = GetBytesPerSample(sample_type_); |
| const auto bytes_per_frame = bytes_per_sample * channels(); |
| |
| if (storage_type_ == kSbMediaAudioFrameStorageTypeInterleaved) { |
| offset_in_bytes_ += frames_to_skip * bytes_per_frame; |
| size_in_bytes_ -= frames_to_skip * bytes_per_frame; |
| timestamp_ += media::AudioFramesToDuration(frames_to_skip, sample_rate); |
| return; |
| } |
| |
| SB_DCHECK(storage_type_ == kSbMediaAudioFrameStorageTypePlanar); |
| |
| Buffer new_storage(size_in_bytes_ - frames_to_skip * bytes_per_frame); |
| const auto new_frames = frames() - frames_to_skip; |
| |
| const uint8_t* source_addr = data(); |
| uint8_t* dest_addr = new_storage.data(); |
| for (int channel = 0; channel < channels(); ++channel) { |
| memcpy(dest_addr, source_addr + bytes_per_sample * frames_to_skip, |
| new_frames * bytes_per_frame); |
| source_addr += frames() * bytes_per_sample; |
| dest_addr += new_frames * bytes_per_sample; |
| } |
| |
| storage_ = std::move(new_storage); |
| timestamp_ += media::AudioFramesToDuration(frames_to_skip, sample_rate); |
| offset_in_bytes_ = 0; |
| size_in_bytes_ = new_frames * bytes_per_frame; |
| } |
| |
| void DecodedAudio::AdjustForDiscardedDurations( |
| int sample_rate, |
| SbTime discarded_duration_from_front, |
| SbTime discarded_duration_from_back) { |
| SB_DCHECK(discarded_duration_from_front >= 0); |
| SB_DCHECK(discarded_duration_from_back >= 0); |
| SB_DCHECK(storage_type() == kSbMediaAudioFrameStorageTypeInterleaved); |
| |
| const auto bytes_per_frame = GetBytesPerSample(sample_type()) * channels_; |
| auto discarded_frames_from_front = |
| AudioDurationToFrames(discarded_duration_from_front, sample_rate); |
| |
| discarded_frames_from_front = std::min(discarded_frames_from_front, frames()); |
| offset_in_bytes_ += bytes_per_frame * discarded_frames_from_front; |
| size_in_bytes_ -= bytes_per_frame * discarded_frames_from_front; |
| timestamp_ += |
| media::AudioFramesToDuration(discarded_frames_from_front, sample_rate); |
| |
| auto discarded_frames_from_back = |
| AudioDurationToFrames(discarded_duration_from_back, sample_rate); |
| discarded_frames_from_back = std::min(discarded_frames_from_back, frames()); |
| size_in_bytes_ -= bytes_per_frame * discarded_frames_from_back; |
| } |
| |
| scoped_refptr<DecodedAudio> DecodedAudio::SwitchFormatTo( |
| SbMediaAudioSampleType new_sample_type, |
| SbMediaAudioFrameStorageType new_storage_type) const { |
| // The caller should call IsFormat() to check before calling SwitchFormatTo(), |
| // as SwitchFormatTo() always copies the whole buffer and is not optimal. |
| SB_DCHECK(new_sample_type != sample_type_ || |
| new_storage_type != storage_type_); |
| |
| if (new_storage_type == storage_type_) { |
| return SwitchSampleTypeTo(new_sample_type); |
| } |
| |
| if (new_sample_type == sample_type_) { |
| return SwitchStorageTypeTo(new_storage_type); |
| } |
| |
| // Both sample types and storage types are different, use the slowest way. |
| int new_size = |
| media::GetBytesPerSample(new_sample_type) * frames() * channels(); |
| scoped_refptr<DecodedAudio> new_decoded_audio = new DecodedAudio( |
| channels(), new_sample_type, new_storage_type, timestamp(), new_size); |
| |
| #define InterleavedSampleAddr(start_addr, channel, frame) \ |
| (start_addr + (frame * channels() + channel)) |
| #define PlanarSampleAddr(start_addr, channel, frame) \ |
| (start_addr + (channel * frames() + frame)) |
| #define GetSampleAddr(StorageType, start_addr, channel, frame) \ |
| (StorageType##SampleAddr(start_addr, channel, frame)) |
| #define SwitchTo(OldSampleType, OldStorageType, NewSampleType, NewStorageType) \ |
| do { \ |
| const OldSampleType* old_samples = \ |
| reinterpret_cast<const OldSampleType*>(this->data()); \ |
| NewSampleType* new_samples = \ |
| reinterpret_cast<NewSampleType*>(new_decoded_audio->data()); \ |
| \ |
| for (int channel = 0; channel < channels(); ++channel) { \ |
| for (int frame = 0; frame < frames(); ++frame) { \ |
| const OldSampleType* old_sample = \ |
| GetSampleAddr(OldStorageType, old_samples, channel, frame); \ |
| NewSampleType* new_sample = \ |
| GetSampleAddr(NewStorageType, new_samples, channel, frame); \ |
| ConvertSample(old_sample, new_sample); \ |
| } \ |
| } \ |
| } while (false) |
| |
| if (sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated && |
| storage_type_ == kSbMediaAudioFrameStorageTypeInterleaved && |
| new_sample_type == kSbMediaAudioSampleTypeFloat32 && |
| new_storage_type == kSbMediaAudioFrameStorageTypePlanar) { |
| SwitchTo(int16_t, Interleaved, float, Planar); |
| } else if (sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated && |
| storage_type_ == kSbMediaAudioFrameStorageTypePlanar && |
| new_sample_type == kSbMediaAudioSampleTypeFloat32 && |
| new_storage_type == kSbMediaAudioFrameStorageTypeInterleaved) { |
| SwitchTo(int16_t, Planar, float, Interleaved); |
| } else if (sample_type_ == kSbMediaAudioSampleTypeFloat32 && |
| storage_type_ == kSbMediaAudioFrameStorageTypeInterleaved && |
| new_sample_type == kSbMediaAudioSampleTypeInt16Deprecated && |
| new_storage_type == kSbMediaAudioFrameStorageTypePlanar) { |
| SwitchTo(float, Interleaved, int16_t, Planar); |
| } else if (sample_type_ == kSbMediaAudioSampleTypeFloat32 && |
| storage_type_ == kSbMediaAudioFrameStorageTypePlanar && |
| new_sample_type == kSbMediaAudioSampleTypeInt16Deprecated && |
| new_storage_type == kSbMediaAudioFrameStorageTypeInterleaved) { |
| SwitchTo(float, Planar, int16_t, Interleaved); |
| } else { |
| SB_NOTREACHED(); |
| } |
| |
| return new_decoded_audio; |
| } |
| |
| scoped_refptr<DecodedAudio> DecodedAudio::Clone() const { |
| scoped_refptr<DecodedAudio> copy = new DecodedAudio( |
| channels(), sample_type(), storage_type(), timestamp(), size_in_bytes()); |
| |
| memcpy(copy->data(), data(), size_in_bytes()); |
| |
| return copy; |
| } |
| |
| scoped_refptr<DecodedAudio> DecodedAudio::SwitchSampleTypeTo( |
| SbMediaAudioSampleType new_sample_type) const { |
| int new_size = |
| media::GetBytesPerSample(new_sample_type) * frames() * channels(); |
| scoped_refptr<DecodedAudio> new_decoded_audio = new DecodedAudio( |
| channels(), new_sample_type, storage_type(), timestamp(), new_size); |
| |
| if (sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated && |
| new_sample_type == kSbMediaAudioSampleTypeFloat32) { |
| const int16_t* old_samples = reinterpret_cast<const int16_t*>(this->data()); |
| float* new_samples = reinterpret_cast<float*>(new_decoded_audio->data()); |
| |
| for (int i = 0; i < frames() * channels(); ++i) { |
| ConvertSample(old_samples + i, new_samples + i); |
| } |
| } else if (sample_type_ == kSbMediaAudioSampleTypeFloat32 && |
| new_sample_type == kSbMediaAudioSampleTypeInt16Deprecated) { |
| const float* old_samples = reinterpret_cast<const float*>(this->data()); |
| int16_t* new_samples = |
| reinterpret_cast<int16_t*>(new_decoded_audio->data()); |
| |
| for (int i = 0; i < frames() * channels(); ++i) { |
| ConvertSample(old_samples + i, new_samples + i); |
| } |
| } |
| |
| return new_decoded_audio; |
| } |
| |
| scoped_refptr<DecodedAudio> DecodedAudio::SwitchStorageTypeTo( |
| SbMediaAudioFrameStorageType new_storage_type) const { |
| scoped_refptr<DecodedAudio> new_decoded_audio = |
| new DecodedAudio(channels(), sample_type(), new_storage_type, timestamp(), |
| size_in_bytes()); |
| int bytes_per_sample = media::GetBytesPerSample(sample_type()); |
| const uint8_t* old_samples = this->data(); |
| uint8_t* new_samples = new_decoded_audio->data(); |
| |
| if (storage_type_ == kSbMediaAudioFrameStorageTypeInterleaved && |
| new_storage_type == kSbMediaAudioFrameStorageTypePlanar) { |
| for (int channel = 0; channel < channels(); ++channel) { |
| for (int frame = 0; frame < frames(); ++frame) { |
| const uint8_t* old_sample = |
| old_samples + (frame * channels() + channel) * bytes_per_sample; |
| uint8_t* new_sample = |
| new_samples + (channel * frames() + frame) * bytes_per_sample; |
| memcpy(new_sample, old_sample, bytes_per_sample); |
| } |
| } |
| } else if (storage_type_ == kSbMediaAudioFrameStorageTypePlanar && |
| new_storage_type == kSbMediaAudioFrameStorageTypeInterleaved) { |
| for (int channel = 0; channel < channels(); ++channel) { |
| for (int frame = 0; frame < frames(); ++frame) { |
| const uint8_t* old_sample = |
| old_samples + (channel * frames() + frame) * bytes_per_sample; |
| uint8_t* new_sample = |
| new_samples + (frame * channels() + channel) * bytes_per_sample; |
| memcpy(new_sample, old_sample, bytes_per_sample); |
| } |
| } |
| } |
| |
| return new_decoded_audio; |
| } |
| |
| bool operator==(const DecodedAudio& left, const DecodedAudio& right) { |
| if (left.is_end_of_stream() && right.is_end_of_stream()) { |
| return true; |
| } |
| if (left.is_end_of_stream() || right.is_end_of_stream()) { |
| return false; |
| } |
| |
| return left.timestamp() == right.timestamp() && |
| left.channels() == right.channels() && |
| left.sample_type() == right.sample_type() && |
| left.storage_type() == right.storage_type() && |
| left.size_in_bytes() == right.size_in_bytes() && |
| memcmp(left.data(), right.data(), right.size_in_bytes()) == 0; |
| } |
| |
| bool operator!=(const DecodedAudio& left, const DecodedAudio& right) { |
| return !(left == right); |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const DecodedAudio& decoded_audio) { |
| if (decoded_audio.is_end_of_stream()) { |
| return os << "(eos)"; |
| } |
| return os << "timestamp: " << decoded_audio.timestamp() |
| << ", channels: " << decoded_audio.channels() << ", sample type: " |
| << GetMediaAudioSampleTypeName(decoded_audio.sample_type()) |
| << ", storage type: " |
| << GetMediaAudioStorageTypeName(decoded_audio.storage_type()) |
| << ", frames: " << decoded_audio.frames(); |
| } |
| |
| } // namespace player |
| } // namespace starboard |
| } // namespace shared |
| } // namespace starboard |