#include "media/muxers/mp4_muxer_delegate_fragment.h"
#include "base/notreached.h"
#include "media/base/decoder_buffer.h"
#include "media/muxers/mp4_muxer_context.h"
namespace media {
namespace {
using mp4::writable_boxes::FragmentSampleFlags;
using mp4::writable_boxes::TrackFragmentHeaderFlags;
using mp4::writable_boxes::TrackFragmentRunFlags;
}
Mp4MuxerDelegateFragment::Mp4MuxerDelegateFragment(Mp4MuxerContext& context,
int video_track_id,
int audio_track_id,
uint32_t sequence_number)
: context_(context), moof_(sequence_number) {
moof_.track_fragments.emplace_back(mp4::writable_boxes::TrackFragment());
moof_.track_fragments.emplace_back(mp4::writable_boxes::TrackFragment());
mdat_.track_data.emplace_back(std::vector<uint8_t>());
mdat_.track_data.emplace_back(std::vector<uint8_t>());
AddNewTrack(kDefaultVideoIndex);
AddNewTrack(kDefaultAudioIndex);
moof_.track_fragments[kDefaultVideoIndex].header.track_id = video_track_id;
moof_.track_fragments[kDefaultVideoIndex].decode_time.track_id =
video_track_id;
moof_.track_fragments[kDefaultAudioIndex].header.track_id = audio_track_id;
moof_.track_fragments[kDefaultAudioIndex].decode_time.track_id =
audio_track_id;
}
bool Mp4MuxerDelegateFragment::HasSamples() const {
for (auto& track : moof_.track_fragments) {
if (track.run.sample_count > 0) {
return true;
}
}
return false;
}
void Mp4MuxerDelegateFragment::AddVideoData(
scoped_refptr<DecoderBuffer> encoded_data,
base::TimeTicks timestamp) {
mp4::writable_boxes::TrackFragmentRun& video_trun =
moof_.track_fragments[kDefaultVideoIndex].run;
AddDataToRun(video_trun, *encoded_data, timestamp);
AddDataToMdat(mdat_.track_data[kDefaultVideoIndex], *encoded_data);
}
void Mp4MuxerDelegateFragment::AddAudioData(
scoped_refptr<DecoderBuffer> encoded_data,
base::TimeTicks timestamp) {
mp4::writable_boxes::TrackFragmentRun& audio_trun =
moof_.track_fragments[kDefaultAudioIndex].run;
AddDataToRun(audio_trun, *encoded_data, timestamp);
AddDataToMdat(mdat_.track_data[kDefaultAudioIndex], *encoded_data);
}
void Mp4MuxerDelegateFragment::AddVideoLastTimestamp(
base::TimeDelta timestamp) {
mp4::writable_boxes::TrackFragmentRun& video_trun =
moof_.track_fragments[kDefaultVideoIndex].run;
AddLastTimestamp(video_trun, timestamp);
}
void Mp4MuxerDelegateFragment::AddAudioLastTimestamp(
base::TimeDelta timestamp) {
mp4::writable_boxes::TrackFragmentRun& audio_trun =
moof_.track_fragments[kDefaultAudioIndex].run;
AddLastTimestamp(audio_trun, timestamp);
}
base::TimeTicks Mp4MuxerDelegateFragment::GetVideoStartTimestamp() const {
if (moof_.track_fragments[kDefaultVideoIndex].run.sample_count == 0) {
return base::TimeTicks();
}
return moof_.track_fragments[kDefaultVideoIndex].run.sample_timestamps[0];
}
const mp4::writable_boxes::MovieFragment&
Mp4MuxerDelegateFragment::GetMovieFragment() const {
return moof_;
}
const mp4::writable_boxes::MediaData& Mp4MuxerDelegateFragment::GetMediaData()
const {
return mdat_;
}
size_t Mp4MuxerDelegateFragment::GetVideoSampleSize() const {
return moof_.track_fragments[kDefaultVideoIndex].run.sample_count;
}
size_t Mp4MuxerDelegateFragment::GetAudioSampleSize() const {
return moof_.track_fragments[kDefaultAudioIndex].run.sample_count;
}
void Mp4MuxerDelegateFragment::Finalize(base::TimeTicks start_audio_time,
base::TimeTicks start_video_time) {
SetBaseDecodeTime(start_audio_time, start_video_time);
bool swap = false;
if (context_->GetAudioTrack().has_value()) {
if (context_->GetAudioTrack().value().index != kDefaultAudioIndex) {
swap = true;
}
} else {
CHECK(context_->GetVideoTrack().has_value());
swap = true;
}
if (swap) {
std::swap(moof_.track_fragments[kDefaultAudioIndex],
moof_.track_fragments[kDefaultVideoIndex]);
std::swap(mdat_.track_data[kDefaultAudioIndex],
mdat_.track_data[kDefaultVideoIndex]);
}
size_t valid_track_count = context_->GetVideoTrack().has_value() ? 1 : 0;
valid_track_count += context_->GetAudioTrack().has_value() ? 1 : 0;
if (valid_track_count == 2) {
return;
} else if (valid_track_count == 1) {
moof_.track_fragments.erase(moof_.track_fragments.begin() + 1);
mdat_.track_data.erase(mdat_.track_data.begin() + 1);
} else {
NOTREACHED();
}
}
void Mp4MuxerDelegateFragment::AddNewTrack(uint32_t track_index) {
bool audio = (track_index == kDefaultAudioIndex);
mp4::writable_boxes::TrackFragment track_fragment;
std::vector<mp4::writable_boxes::FragmentSampleFlags> sample_flags;
if (audio) {
sample_flags.emplace_back(FragmentSampleFlags::kSampleFlagDependsNo);
} else {
sample_flags.emplace_back(FragmentSampleFlags::kSampleFlagIsNonSync);
sample_flags.emplace_back(FragmentSampleFlags::kSampleFlagDependsYes);
}
track_fragment.header.default_sample_flags =
BuildFlags<mp4::writable_boxes::FragmentSampleFlags>(sample_flags);
track_fragment.header.default_sample_duration = base::TimeDelta();
track_fragment.header.default_sample_size = 0;
std::vector<mp4::writable_boxes::TrackFragmentHeaderFlags>
fragment_header_flags = {
TrackFragmentHeaderFlags::kDefaultBaseIsMoof,
TrackFragmentHeaderFlags::kkDefaultSampleFlagsPresent
};
track_fragment.header.flags =
BuildFlags<mp4::writable_boxes::TrackFragmentHeaderFlags>(
fragment_header_flags);
track_fragment.run = {};
track_fragment.run.sample_count = 0;
std::vector<mp4::writable_boxes::TrackFragmentRunFlags> fragment_run_flags = {
TrackFragmentRunFlags::kDataOffsetPresent,
TrackFragmentRunFlags::kSampleDurationPresent,
TrackFragmentRunFlags::kSampleSizePresent};
if (audio) {
track_fragment.run.first_sample_flags = 0u;
} else {
fragment_run_flags.emplace_back(
TrackFragmentRunFlags::kFirstSampleFlagsPresent);
track_fragment.run.first_sample_flags = static_cast<uint32_t>(
mp4::writable_boxes::FragmentSampleFlags::kSampleFlagDependsNo);
}
track_fragment.run.flags =
BuildFlags<mp4::writable_boxes::TrackFragmentRunFlags>(
fragment_run_flags);
moof_.track_fragments[track_index] = std::move(track_fragment);
}
void Mp4MuxerDelegateFragment::AddDataToRun(
mp4::writable_boxes::TrackFragmentRun& trun,
const DecoderBuffer& encoded_data,
base::TimeTicks timestamp) {
trun.sample_count += 1;
trun.sample_sizes.emplace_back(encoded_data.size());
trun.sample_timestamps.emplace_back(timestamp);
}
void Mp4MuxerDelegateFragment::AddDataToMdat(
std::vector<uint8_t>& track_data,
const DecoderBuffer& encoded_data) {
size_t current_size = track_data.size();
auto encoded_data_span = base::span(encoded_data);
if (current_size + encoded_data_span.size() > track_data.capacity()) {
track_data.reserve((current_size + encoded_data_span.size()) * 1.5);
}
track_data.resize(current_size + encoded_data_span.size());
base::span(track_data)
.subspan(current_size)
.copy_from_nonoverlapping(encoded_data_span);
}
void Mp4MuxerDelegateFragment::AddLastTimestamp(
mp4::writable_boxes::TrackFragmentRun& trun,
base::TimeDelta timestamp) {
if (trun.sample_timestamps.empty()) {
return;
}
if (trun.sample_timestamps.size() > trun.sample_count) {
return;
}
base::TimeTicks last_timestamp_entry = trun.sample_timestamps.back();
trun.sample_timestamps.emplace_back(last_timestamp_entry + timestamp);
}
void Mp4MuxerDelegateFragment::SetBaseDecodeTime(
base::TimeTicks start_audio_time,
base::TimeTicks start_video_time) {
if (moof_.track_fragments[kDefaultAudioIndex].run.sample_count > 0) {
moof_.track_fragments[kDefaultAudioIndex]
.decode_time.base_media_decode_time =
moof_.track_fragments[kDefaultAudioIndex].run.sample_timestamps[0] -
start_audio_time;
}
if (moof_.track_fragments[kDefaultVideoIndex].run.sample_count > 0) {
moof_.track_fragments[kDefaultVideoIndex]
.decode_time.base_media_decode_time =
moof_.track_fragments[kDefaultVideoIndex].run.sample_timestamps[0] -
start_video_time;
}
}
}