1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
media / muxers / mp4_muxer.cc [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/muxers/mp4_muxer.h"
#include <algorithm>
#include <memory>
#include "base/logging.h"
#include "base/time/time.h"
namespace media {
namespace {
// Force new MP4 blob at a maximum rate of 10 Hz.
constexpr base::TimeDelta kMinimumForcedBlobDuration = base::Seconds(1);
} // namespace
Mp4Muxer::Mp4Muxer(AudioCodec audio_codec,
bool has_video,
bool has_audio,
std::unique_ptr<Mp4MuxerDelegateInterface> delegate,
std::optional<base::TimeDelta> max_data_output_interval)
: mp4_muxer_delegate_(std::move(delegate)),
max_data_output_interval_(
std::max(max_data_output_interval.value_or(base::TimeDelta()),
kMinimumForcedBlobDuration)),
has_video_(has_video),
has_audio_(has_audio),
audio_codec_(audio_codec) {
CHECK(has_video_ || has_audio_);
CHECK(!has_audio || audio_codec_ == AudioCodec::kAAC ||
audio_codec_ == AudioCodec::kOpus);
DVLOG(1) << __func__ << ", Max output interval in seconds: "
<< max_data_output_interval_.InSeconds();
// Creation can be done on a different sequence than main activities.
DETACH_FROM_SEQUENCE(sequence_checker_);
}
Mp4Muxer::~Mp4Muxer() = default;
bool Mp4Muxer::PutFrame(EncodedFrame frame,
base::TimeDelta relative_timestamp) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AudioParameters* audio_params = absl::get_if<AudioParameters>(&frame.params);
if (audio_params) {
CHECK(has_audio_);
// The first audio sample should have code description.
mp4_muxer_delegate_->AddAudioFrame(*audio_params, std::move(frame.data),
frame.codec_description,
base::TimeTicks() + relative_timestamp);
seen_audio_ = true;
if (!has_video_) {
// If there is no video, we can try flush the fragment regardless of
// video key frame.
MaybeForceFragmentFlush();
}
} else {
auto* video_params = absl::get_if<VideoParameters>(&frame.params);
CHECK(video_params);
CHECK(has_video_);
// The `trun` box, which holds information for each sample such as duration
// and size, cannot be split because the `count` property needs to have the
// exact number of sample entries. The unit of the `trun` box is per
// fragment, which is based on the video key frame. So, it checks flush
// only when the next key frame arrives.
if (frame.data->is_key_frame()) {
MaybeForceFragmentFlush();
}
mp4_muxer_delegate_->AddVideoFrame(*video_params, std::move(frame.data),
frame.codec_description,
base::TimeTicks() + relative_timestamp);
}
return true;
}
void Mp4Muxer::MaybeForceFragmentFlush() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// It follows pattern of webm muxer where it does not respect
// interval flush time unless video stream exists.
if (max_data_output_interval_.is_zero()) {
return;
}
if (start_or_lastest_flushed_time_.is_null()) {
start_or_lastest_flushed_time_ = base::TimeTicks::Now();
return;
}
if (base::TimeTicks::Now() - start_or_lastest_flushed_time_ >=
max_data_output_interval_) {
mp4_muxer_delegate_->FlushFragment();
start_or_lastest_flushed_time_ = base::TimeTicks::Now();
}
}
bool Mp4Muxer::Flush() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(1) << __func__ << ", Flush called ";
if (!mp4_muxer_delegate_->Flush()) {
DVLOG(1) << __func__ << ", Flush failed ";
return false;
}
start_or_lastest_flushed_time_ = base::TimeTicks::Now();
return true;
}
} // namespace media