| // Copyright 2018 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/video_dmp_reader.h" |
| |
| #include <algorithm> |
| #include <functional> |
| |
| namespace starboard { |
| namespace shared { |
| namespace starboard { |
| namespace player { |
| namespace video_dmp { |
| |
| namespace { |
| |
| template <typename AccessUnit> |
| int64_t CalculateAverageBitrate(const std::vector<AccessUnit>& access_units) { |
| if (access_units.empty()) { |
| return 0; |
| } |
| |
| if (access_units.size() == 1) { |
| // A guestimated bitrate of 1k. |
| return 1024; |
| } |
| |
| SbTime duration = |
| access_units.back().timestamp() - access_units.front().timestamp(); |
| |
| SB_DCHECK(duration > 0); |
| |
| int64_t total_bitrate = 0; |
| for (auto& au : access_units) { |
| total_bitrate += au.data().size(); |
| } |
| |
| return total_bitrate * 8 * kSbTimeSecond / duration; |
| } |
| |
| static void DeallocateSampleFunc(SbPlayer player, |
| void* context, |
| const void* sample_buffer) {} |
| |
| SbPlayerSampleInfo ConvertToPlayerSampleInfo( |
| const VideoDmpReader::AudioAccessUnit& audio_unit) { |
| SbPlayerSampleInfo sample_info = {}; |
| sample_info.buffer = audio_unit.data().data(); |
| sample_info.buffer_size = static_cast<int>(audio_unit.data().size()); |
| sample_info.timestamp = audio_unit.timestamp(); |
| sample_info.drm_info = audio_unit.drm_sample_info(); |
| sample_info.type = kSbMediaTypeAudio; |
| audio_unit.audio_sample_info().ConvertTo(&sample_info.audio_sample_info); |
| return sample_info; |
| } |
| |
| SbPlayerSampleInfo ConvertToPlayerSampleInfo( |
| const VideoDmpReader::VideoAccessUnit& video_unit) { |
| SbPlayerSampleInfo sample_info = {}; |
| sample_info.buffer = video_unit.data().data(); |
| sample_info.buffer_size = static_cast<int>(video_unit.data().size()); |
| sample_info.timestamp = video_unit.timestamp(); |
| sample_info.drm_info = video_unit.drm_sample_info(); |
| sample_info.type = kSbMediaTypeVideo; |
| video_unit.video_sample_info().ConvertTo(&sample_info.video_sample_info); |
| return sample_info; |
| } |
| |
| } // namespace |
| |
| using std::placeholders::_1; |
| using std::placeholders::_2; |
| |
| bool VideoDmpReader::Registry::GetDmpInfo(const char* filename, |
| DmpInfo* dmp_info) const { |
| SB_DCHECK(filename); |
| SB_DCHECK(dmp_info); |
| |
| ScopedLock scoped_lock(mutex_); |
| auto iter = dmp_infos_.find(filename); |
| if (iter == dmp_infos_.end()) { |
| return false; |
| } |
| *dmp_info = iter->second; |
| return true; |
| } |
| |
| void VideoDmpReader::Registry::Register(const char* filename, |
| const DmpInfo& dmp_info) { |
| SB_DCHECK(filename); |
| |
| ScopedLock scoped_lock(mutex_); |
| SB_DCHECK(dmp_infos_.find(filename) == dmp_infos_.end()); |
| dmp_infos_[filename] = dmp_info; |
| } |
| |
| VideoDmpReader::VideoDmpReader( |
| const char* filename, |
| ReadOnDemandOptions read_on_demand_options /*= kDisableReadOnDemand*/) |
| : file_reader_(filename, 1024 * 1024), |
| read_cb_(std::bind(&FileCacheReader::Read, &file_reader_, _1, _2)), |
| allow_read_on_demand_(read_on_demand_options == kEnableReadOnDemand) { |
| bool already_cached = GetRegistry()->GetDmpInfo(filename, &dmp_info_); |
| |
| if (already_cached && allow_read_on_demand_) { |
| // This is necessary as the current implementation assumes that the address |
| // of any access units are never changed during the life time of the object, |
| // and keep using it without explicit reference. |
| audio_access_units_.reserve(dmp_info_.audio_access_units_size); |
| video_access_units_.reserve(dmp_info_.video_access_units_size); |
| return; |
| } |
| |
| Parse(); |
| |
| if (!already_cached) { |
| GetRegistry()->Register(filename, dmp_info_); |
| } |
| } |
| |
| VideoDmpReader::~VideoDmpReader() {} |
| |
| std::string VideoDmpReader::audio_mime_type() const { |
| if (dmp_info_.audio_codec == kSbMediaAudioCodecNone) { |
| return ""; |
| } |
| std::stringstream ss; |
| switch (dmp_info_.audio_codec) { |
| case kSbMediaAudioCodecAac: |
| ss << "audio/mp4; codecs=\"mp4a.40.2\";"; |
| break; |
| case kSbMediaAudioCodecOpus: |
| ss << "audio/webm; codecs=\"opus\";"; |
| break; |
| case kSbMediaAudioCodecAc3: |
| ss << "audio/mp4; codecs=\"ac-3\";"; |
| break; |
| case kSbMediaAudioCodecEac3: |
| ss << "audio/mp4; codecs=\"ec-3\";"; |
| break; |
| default: |
| SB_NOTREACHED(); |
| } |
| |
| ss << " channels=" |
| << dmp_info_.audio_sample_info.stream_info.number_of_channels; |
| return ss.str(); |
| } |
| |
| std::string VideoDmpReader::video_mime_type() { |
| if (dmp_info_.video_codec == kSbMediaVideoCodecNone) { |
| return ""; |
| } |
| // TODO: Support HDR |
| std::stringstream ss; |
| switch (dmp_info_.video_codec) { |
| case kSbMediaVideoCodecH264: |
| ss << "video/mp4; codecs=\"avc1.4d402a\";"; |
| break; |
| case kSbMediaVideoCodecVp9: |
| ss << "video/webm; codecs=\"vp9\";"; |
| break; |
| case kSbMediaVideoCodecAv1: |
| ss << "video/mp4; codecs=\"av01.0.08M.08\";"; |
| break; |
| default: |
| SB_NOTREACHED(); |
| } |
| if (number_of_video_buffers() > 0) { |
| const auto& video_stream_info = this->video_stream_info(); |
| ss << "width=" << video_stream_info.frame_width |
| << "; height=" << video_stream_info.frame_height << ";"; |
| } |
| ss << " framerate=" << dmp_info_.video_fps; |
| return ss.str(); |
| } |
| |
| SbPlayerSampleInfo VideoDmpReader::GetPlayerSampleInfo(SbMediaType type, |
| size_t index) { |
| EnsureSampleLoaded(type, index); |
| |
| switch (type) { |
| case kSbMediaTypeAudio: { |
| SB_DCHECK(index < audio_access_units_.size()); |
| const AudioAccessUnit& audio_au = audio_access_units_[index]; |
| return ConvertToPlayerSampleInfo(audio_au); |
| } |
| case kSbMediaTypeVideo: { |
| SB_DCHECK(index < video_access_units_.size()); |
| const VideoAccessUnit& video_au = video_access_units_[index]; |
| return ConvertToPlayerSampleInfo(video_au); |
| } |
| } |
| SB_NOTREACHED() << "Unhandled SbMediaType"; |
| return SbPlayerSampleInfo(); |
| } |
| |
| const media::AudioSampleInfo& VideoDmpReader::GetAudioSampleInfo(size_t index) { |
| EnsureSampleLoaded(kSbMediaTypeAudio, index); |
| |
| SB_DCHECK(index < audio_access_units_.size()); |
| const AudioAccessUnit& au = audio_access_units_[index]; |
| return au.audio_sample_info(); |
| } |
| |
| void VideoDmpReader::ParseHeader(uint32_t* dmp_writer_version) { |
| SB_DCHECK(dmp_writer_version); |
| SB_DCHECK(!reverse_byte_order_.has_engaged()); |
| |
| int64_t file_size = file_reader_.GetSize(); |
| SB_CHECK(file_size >= 0); |
| |
| reverse_byte_order_ = false; |
| uint32_t byte_order_mark; |
| Read(read_cb_, reverse_byte_order_.value(), &byte_order_mark); |
| if (byte_order_mark != kByteOrderMark) { |
| std::reverse(reinterpret_cast<uint8_t*>(&byte_order_mark), |
| reinterpret_cast<uint8_t*>(&byte_order_mark + 1)); |
| SB_CHECK(byte_order_mark == kByteOrderMark); |
| reverse_byte_order_ = true; |
| } |
| |
| Read(read_cb_, reverse_byte_order_.value(), dmp_writer_version); |
| } |
| |
| bool VideoDmpReader::ParseOneRecord() { |
| uint32_t type; |
| int bytes_read = read_cb_(&type, sizeof(type)); |
| if (bytes_read != sizeof(type)) { |
| // Read an invalid number of bytes (corrupt file), or we read zero bytes |
| // (end of file). Return false to signal the end of reading. |
| return false; |
| } |
| if (reverse_byte_order_.value()) { |
| std::reverse(reinterpret_cast<uint8_t*>(&type), |
| reinterpret_cast<uint8_t*>(&type + 1)); |
| } |
| switch (type) { |
| case kRecordTypeAudioConfig: |
| Read(read_cb_, reverse_byte_order_.value(), &dmp_info_.audio_codec); |
| if (dmp_info_.audio_codec != kSbMediaAudioCodecNone) { |
| Read(read_cb_, reverse_byte_order_.value(), |
| &dmp_info_.audio_sample_info); |
| SB_DCHECK(dmp_info_.audio_codec == |
| dmp_info_.audio_sample_info.stream_info.codec); |
| } |
| break; |
| case kRecordTypeVideoConfig: |
| Read(read_cb_, reverse_byte_order_.value(), &dmp_info_.video_codec); |
| break; |
| case kRecordTypeAudioAccessUnit: |
| audio_access_units_.push_back(ReadAudioAccessUnit()); |
| break; |
| case kRecordTypeVideoAccessUnit: |
| video_access_units_.push_back(ReadVideoAccessUnit()); |
| break; |
| default: |
| SB_NOTREACHED() << type; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void VideoDmpReader::Parse() { |
| SB_DCHECK(!reverse_byte_order_.has_engaged()); |
| |
| uint32_t dmp_writer_version = 0; |
| ParseHeader(&dmp_writer_version); |
| if (dmp_writer_version != kSupportedWriterVersion) { |
| SB_LOG(ERROR) << "Unsupported input dmp file(" << dmp_writer_version |
| << "). Please regenerate dmp files with" |
| << " right dmp writer. Currently support version " |
| << kSupportedWriterVersion << "."; |
| return; |
| } |
| |
| while (ParseOneRecord()) { |
| } |
| |
| dmp_info_.audio_access_units_size = audio_access_units_.size(); |
| dmp_info_.audio_bitrate = CalculateAverageBitrate(audio_access_units_); |
| dmp_info_.video_access_units_size = video_access_units_.size(); |
| dmp_info_.video_bitrate = CalculateAverageBitrate(video_access_units_); |
| |
| // Guestimate the audio duration. |
| if (audio_access_units_.size() > 1) { |
| auto frame_duration = |
| audio_access_units_[audio_access_units_.size() - 1].timestamp() - |
| audio_access_units_[audio_access_units_.size() - 2].timestamp(); |
| dmp_info_.audio_duration = |
| audio_access_units_.back().timestamp() + frame_duration; |
| } |
| // Guestimate the video fps. |
| if (video_access_units_.size() > 1) { |
| SbTime first_timestamp = video_access_units_.front().timestamp(); |
| SbTime second_timestamp = video_access_units_.back().timestamp(); |
| for (const auto& au : video_access_units_) { |
| if (au.timestamp() != first_timestamp && |
| au.timestamp() < second_timestamp) { |
| second_timestamp = au.timestamp(); |
| } |
| } |
| SB_DCHECK(first_timestamp < second_timestamp); |
| SbTime frame_duration = second_timestamp - first_timestamp; |
| dmp_info_.video_fps = kSbTimeSecond / frame_duration; |
| |
| SbTime last_frame_timestamp = video_access_units_.back().timestamp(); |
| for (auto it = video_access_units_.rbegin(); |
| it != video_access_units_.rend(); it++) { |
| if (it->timestamp() > last_frame_timestamp) { |
| last_frame_timestamp = it->timestamp(); |
| } |
| if (it->video_sample_info().is_key_frame) { |
| break; |
| } |
| } |
| dmp_info_.video_duration = last_frame_timestamp + frame_duration; |
| } |
| } |
| |
| void VideoDmpReader::EnsureSampleLoaded(SbMediaType type, size_t index) { |
| if (!reverse_byte_order_.has_engaged()) { |
| uint32_t dmp_writer_version = 0; |
| ParseHeader(&dmp_writer_version); |
| SB_DCHECK(dmp_writer_version == kSupportedWriterVersion); |
| } |
| |
| if (type == kSbMediaTypeAudio) { |
| while (index >= audio_access_units_.size() && ParseOneRecord()) { |
| } |
| SB_CHECK(index < audio_access_units_.size()); |
| } else { |
| SB_DCHECK(type == kSbMediaTypeVideo); |
| while (index >= video_access_units_.size() && ParseOneRecord()) { |
| } |
| SB_CHECK(index < video_access_units_.size()); |
| } |
| } |
| |
| VideoDmpReader::AudioAccessUnit VideoDmpReader::ReadAudioAccessUnit() { |
| SbTime timestamp; |
| Read(read_cb_, reverse_byte_order_.value(), ×tamp); |
| |
| bool drm_sample_info_present; |
| Read(read_cb_, reverse_byte_order_.value(), &drm_sample_info_present); |
| |
| SbDrmSampleInfoWithSubSampleMapping drm_sample_info; |
| if (drm_sample_info_present) { |
| Read(read_cb_, reverse_byte_order_.value(), &drm_sample_info); |
| } |
| |
| uint32_t size; |
| Read(read_cb_, reverse_byte_order_.value(), &size); |
| std::vector<uint8_t> data(size); |
| Read(read_cb_, data.data(), size); |
| |
| media::AudioSampleInfo audio_sample_info; |
| Read(read_cb_, reverse_byte_order_.value(), &audio_sample_info); |
| |
| return AudioAccessUnit(timestamp, |
| drm_sample_info_present ? &drm_sample_info : NULL, |
| std::move(data), std::move(audio_sample_info)); |
| } |
| |
| VideoDmpReader::VideoAccessUnit VideoDmpReader::ReadVideoAccessUnit() { |
| SbTime timestamp; |
| Read(read_cb_, reverse_byte_order_.value(), ×tamp); |
| |
| bool drm_sample_info_present; |
| Read(read_cb_, reverse_byte_order_.value(), &drm_sample_info_present); |
| |
| SbDrmSampleInfoWithSubSampleMapping drm_sample_info; |
| if (drm_sample_info_present) { |
| Read(read_cb_, reverse_byte_order_.value(), &drm_sample_info); |
| } |
| |
| uint32_t size; |
| Read(read_cb_, reverse_byte_order_.value(), &size); |
| std::vector<uint8_t> data(size); |
| Read(read_cb_, data.data(), size); |
| |
| media::VideoSampleInfo video_sample_info; |
| Read(read_cb_, reverse_byte_order_.value(), &video_sample_info); |
| |
| return VideoAccessUnit(timestamp, |
| drm_sample_info_present ? &drm_sample_info : NULL, |
| std::move(data), std::move(video_sample_info)); |
| } |
| |
| // static |
| VideoDmpReader::Registry* VideoDmpReader::GetRegistry() { |
| static Registry s_registry; |
| return &s_registry; |
| } |
| |
| } // namespace video_dmp |
| } // namespace player |
| } // namespace starboard |
| } // namespace shared |
| } // namespace starboard |