OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "media/capture/webm_muxer.h" | 5 #include "media/capture/webm_muxer.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "media/audio/audio_parameters.h" | |
8 #include "media/base/limits.h" | 9 #include "media/base/limits.h" |
9 #include "media/base/video_frame.h" | 10 #include "media/base/video_frame.h" |
10 #include "ui/gfx/geometry/size.h" | 11 #include "ui/gfx/geometry/size.h" |
11 | 12 |
12 namespace media { | 13 namespace media { |
13 | 14 |
15 namespace { | |
16 | |
17 // Comments and constants from media/filters/opus_audio_decoder.cc | |
mcasas
2015/10/28 00:42:02
Please refactor the common constants out of both o
ajose
2015/10/28 23:43:47
Done.
| |
18 enum { | |
19 // Size of the Opus extra data excluding optional mapping information. | |
20 OPUS_EXTRADATA_SIZE = 19, | |
21 // Offset for "OpusHead" (64 bits) | |
22 OPUS_EXTRADATA_LABEL_OFFSET = 0, | |
23 // Offset to the Opus version number (8 bits) | |
24 OPUS_EXTRADATA_VERSION_OFFSET = 8, | |
25 // Offset to the channel count byte in the Opus extra data (8 bits) | |
26 OPUS_EXTRADATA_CHANNELS_OFFSET = 9, | |
27 // Offset to the pre-skip value in the Opus extra data (16 bits) | |
28 OPUS_EXTRADATA_SKIP_SAMPLES_OFFSET = 10, | |
29 // Offset to the sampling rate value in the Opus extra data (32 bits) | |
30 OPUS_EXTRADATA_SAMPLE_RATE_OFFSET = 12, | |
31 // Offset to the gain value in the Opus extra data (16 bits) | |
32 OPUS_EXTRADATA_GAIN_OFFSET = 16, | |
33 // Offset to the channel mapping byte in the Opus extra data (8 bits) | |
34 OPUS_EXTRADATA_CHANNEL_MAPPING_OFFSET = 18, | |
35 | |
36 // Extra Data contains a stream map, beyond the always present | |
37 // |OPUS_EXTRADATA_SIZE| bytes of data. The mapping data contains stream | |
38 // count, coupling information, and per channel mapping values: | |
39 // - Byte 0: Number of streams. | |
40 // - Byte 1: Number coupled. | |
41 // - Byte 2: Starting at byte 2 are |extra_data->channels| uint8 mapping | |
42 // values. | |
43 OPUS_EXTRADATA_NUM_STREAMS_OFFSET = OPUS_EXTRADATA_SIZE, | |
44 OPUS_EXTRADATA_NUM_COUPLED_OFFSET = OPUS_EXTRADATA_NUM_STREAMS_OFFSET + 1, | |
45 OPUS_EXTRADATA_STREAM_MAP_OFFSET = OPUS_EXTRADATA_NUM_STREAMS_OFFSET + 2, | |
46 }; | |
47 | |
48 // Opus internal to Vorbis channel order mapping written in the header. | |
49 static const uint8_t opus_vorbis_channel_map[8][8] = { | |
50 {0}, | |
51 {0, 1}, | |
52 {0, 2, 1}, | |
53 {0, 1, 2, 3}, | |
54 {0, 4, 1, 2, 3}, | |
55 {0, 4, 1, 2, 3, 5}, | |
56 {0, 4, 1, 2, 3, 5, 6}, | |
57 {0, 6, 1, 2, 3, 4, 5, 7}, | |
58 }; | |
59 | |
60 void WriteOpusHeader(const media::AudioParameters& params, uint8* header) { | |
61 // See https://wiki.xiph.org/OggOpus#ID_Header. | |
62 // Set magic signature. | |
63 std::string label = "OpusHead"; | |
64 memcpy(header + OPUS_EXTRADATA_LABEL_OFFSET, &label, label.size()); | |
65 // Set Opus version. | |
66 header[OPUS_EXTRADATA_VERSION_OFFSET] = 1; | |
67 // Set channel count. | |
68 header[OPUS_EXTRADATA_CHANNELS_OFFSET] = params.channels(); | |
69 // Set pre-skip | |
70 uint16 skip = 0; | |
71 memcpy(header + OPUS_EXTRADATA_SKIP_SAMPLES_OFFSET, &skip, sizeof(uint16)); | |
72 // Set original input sample rate in Hz. | |
73 uint32 sample_rate = params.sample_rate(); | |
74 memcpy(header + OPUS_EXTRADATA_SAMPLE_RATE_OFFSET, &sample_rate, | |
75 sizeof(uint32)); | |
76 // Set output gain in dB. | |
77 uint16 gain = 0; | |
78 memcpy(header + OPUS_EXTRADATA_GAIN_OFFSET, &gain, 2); | |
79 | |
80 // Set channel mapping. | |
81 if (params.channels() > 2) { | |
82 // Also possible to have a multistream, not supported for now. | |
83 DCHECK_LE(params.channels(), 8); | |
mcasas
2015/10/28 00:42:02
Extract |8| here and in l.49 to a constant?
ajose
2015/10/28 23:43:47
Done.
| |
84 header[OPUS_EXTRADATA_CHANNEL_MAPPING_OFFSET] = 1; | |
85 // Assuming no coupled streams. This should actually be | |
86 // channels() - |coupled_streams|. | |
87 header[OPUS_EXTRADATA_NUM_STREAMS_OFFSET] = params.channels(); | |
88 header[OPUS_EXTRADATA_NUM_COUPLED_OFFSET] = 0; | |
89 // Set the actual stream map. | |
90 memcpy(header + OPUS_EXTRADATA_STREAM_MAP_OFFSET, | |
91 opus_vorbis_channel_map[params.channels() - 1], params.channels()); | |
92 } else { | |
93 header[OPUS_EXTRADATA_CHANNEL_MAPPING_OFFSET] = 0; | |
94 } | |
95 } | |
96 | |
14 static double GetFrameRate(const scoped_refptr<VideoFrame>& video_frame) { | 97 static double GetFrameRate(const scoped_refptr<VideoFrame>& video_frame) { |
15 const double kZeroFrameRate = 0.0; | 98 const double kZeroFrameRate = 0.0; |
16 const double kDefaultFrameRate = 30.0; | 99 const double kDefaultFrameRate = 30.0; |
17 | 100 |
18 double frame_rate = kDefaultFrameRate; | 101 double frame_rate = kDefaultFrameRate; |
19 if (!video_frame->metadata()->GetDouble( | 102 if (!video_frame->metadata()->GetDouble( |
20 VideoFrameMetadata::FRAME_RATE, &frame_rate) || | 103 VideoFrameMetadata::FRAME_RATE, &frame_rate) || |
21 frame_rate <= kZeroFrameRate || | 104 frame_rate <= kZeroFrameRate || |
22 frame_rate > media::limits::kMaxFramesPerSecond) { | 105 frame_rate > media::limits::kMaxFramesPerSecond) { |
23 frame_rate = kDefaultFrameRate; | 106 frame_rate = kDefaultFrameRate; |
24 } | 107 } |
25 return frame_rate; | 108 return frame_rate; |
26 } | 109 } |
27 | 110 |
28 WebmMuxer::WebmMuxer(VideoCodec codec, const WriteDataCB& write_data_callback) | 111 } // anonymous namespace |
112 | |
113 WebmMuxer::WebmMuxer(VideoCodec codec, | |
114 size_t num_video_tracks, | |
115 size_t num_audio_tracks, | |
116 const WriteDataCB& write_data_callback) | |
29 : use_vp9_(codec == kCodecVP9), | 117 : use_vp9_(codec == kCodecVP9), |
30 track_index_(0), | 118 video_track_index_(0), |
119 audio_track_index_(0), | |
120 has_video_(num_video_tracks > 0), | |
121 has_audio_(num_audio_tracks > 0), | |
31 write_data_callback_(write_data_callback), | 122 write_data_callback_(write_data_callback), |
32 position_(0) { | 123 position_(0) { |
33 DCHECK(!write_data_callback_.is_null()); | 124 DCHECK(!write_data_callback_.is_null()); |
34 DCHECK(codec == kCodecVP8 || codec == kCodecVP9) | 125 DCHECK(codec == kCodecVP8 || codec == kCodecVP9) |
35 << " Only Vp8 and VP9 are supported in WebmMuxer"; | 126 << " Only Vp8 and VP9 are supported in WebmMuxer"; |
127 | |
mcasas
2015/10/28 00:42:02
Consider adding DCHECK(has_video_ || has_audio_);
ajose
2015/10/28 23:43:47
Done.
| |
128 segment_.Init(this); | |
129 segment_.set_mode(mkvmuxer::Segment::kLive); | |
130 segment_.OutputCues(false); | |
131 | |
132 mkvmuxer::SegmentInfo* const info = segment_.GetSegmentInfo(); | |
133 info->set_writing_app("Chrome"); | |
134 info->set_muxing_app("Chrome"); | |
135 | |
36 // Creation is done on a different thread than main activities. | 136 // Creation is done on a different thread than main activities. |
37 thread_checker_.DetachFromThread(); | 137 thread_checker_.DetachFromThread(); |
38 } | 138 } |
39 | 139 |
40 WebmMuxer::~WebmMuxer() { | 140 WebmMuxer::~WebmMuxer() { |
41 // No need to segment_.Finalize() since is not Seekable(), i.e. a live | 141 // No need to segment_.Finalize() since is not Seekable(), i.e. a live |
42 // stream, but is a good practice. | 142 // stream, but is a good practice. |
43 DCHECK(thread_checker_.CalledOnValidThread()); | 143 DCHECK(thread_checker_.CalledOnValidThread()); |
44 segment_.Finalize(); | 144 segment_.Finalize(); |
45 } | 145 } |
46 | 146 |
47 void WebmMuxer::OnEncodedVideo(const scoped_refptr<VideoFrame>& video_frame, | 147 void WebmMuxer::OnEncodedVideo(const scoped_refptr<VideoFrame>& video_frame, |
48 scoped_ptr<std::string> encoded_data, | 148 scoped_ptr<std::string> encoded_data, |
49 base::TimeTicks timestamp, | 149 base::TimeTicks timestamp, |
50 bool is_key_frame) { | 150 bool is_key_frame) { |
51 DVLOG(1) << __FUNCTION__ << " - " << encoded_data->size() << "B"; | 151 DVLOG(1) << __FUNCTION__ << " - " << encoded_data->size() << "B"; |
52 DCHECK(thread_checker_.CalledOnValidThread()); | 152 DCHECK(thread_checker_.CalledOnValidThread()); |
53 if (!track_index_) { | 153 |
154 if (!video_track_index_) { | |
54 // |track_index_|, cannot be zero (!), initialize WebmMuxer in that case. | 155 // |track_index_|, cannot be zero (!), initialize WebmMuxer in that case. |
55 // http://www.matroska.org/technical/specs/index.html#Tracks | 156 // http://www.matroska.org/technical/specs/index.html#Tracks |
56 AddVideoTrack(video_frame->visible_rect().size(), | 157 AddVideoTrack(video_frame->visible_rect().size(), |
57 GetFrameRate(video_frame)); | 158 GetFrameRate(video_frame)); |
58 first_frame_timestamp_ = timestamp; | 159 if (first_frame_timestamp_ == base::TimeTicks()) |
mcasas
2015/10/28 00:42:02
I'd say
if (first_frame_timestamp_.is_null())
ajose
2015/10/28 23:43:47
Done.
| |
160 first_frame_timestamp_ = timestamp; | |
59 } | 161 } |
162 | |
163 // TODO(ajose): Don't drop data. http://crbug.com/547948 | |
164 if (has_audio_ && !audio_track_index_) { | |
165 DVLOG(1) << __FUNCTION__ << ": delaying until audio track ready."; | |
166 return; | |
167 } | |
168 | |
169 most_recent_timestamp_ = | |
170 std::max(most_recent_timestamp_, timestamp - first_frame_timestamp_); | |
60 segment_.AddFrame(reinterpret_cast<const uint8_t*>(encoded_data->data()), | 171 segment_.AddFrame(reinterpret_cast<const uint8_t*>(encoded_data->data()), |
61 encoded_data->size(), | 172 encoded_data->size(), video_track_index_, |
62 track_index_, | 173 most_recent_timestamp_.InMicroseconds() * |
63 (timestamp - first_frame_timestamp_).InMicroseconds() * | |
64 base::Time::kNanosecondsPerMicrosecond, | 174 base::Time::kNanosecondsPerMicrosecond, |
65 is_key_frame); | 175 is_key_frame); |
66 } | 176 } |
67 | 177 |
178 void WebmMuxer::OnEncodedAudio(const media::AudioParameters& params, | |
179 scoped_ptr<std::string> encoded_data, | |
180 base::TimeTicks timestamp) { | |
181 DVLOG(1) << __FUNCTION__ << " - " << encoded_data->size() << "B"; | |
182 DCHECK(thread_checker_.CalledOnValidThread()); | |
183 | |
184 if (!audio_track_index_) { | |
185 AddAudioTrack(params); | |
186 if (first_frame_timestamp_ == base::TimeTicks()) | |
187 first_frame_timestamp_ = timestamp; | |
188 } | |
189 | |
190 // TODO(ajose): Don't drop data. http://crbug.com/547948 | |
191 if (has_video_ && !video_track_index_) { | |
192 DVLOG(1) << __FUNCTION__ << ": delaying until video track ready."; | |
193 return; | |
194 } | |
195 | |
196 most_recent_timestamp_ = | |
197 std::max(most_recent_timestamp_, timestamp - first_frame_timestamp_); | |
198 segment_.AddFrame(reinterpret_cast<const uint8_t*>(encoded_data->data()), | |
199 encoded_data->size(), audio_track_index_, | |
200 most_recent_timestamp_.InMicroseconds() * | |
201 base::Time::kNanosecondsPerMicrosecond, | |
202 true /* is_key_frame -- always true for audio */); | |
203 } | |
204 | |
68 void WebmMuxer::AddVideoTrack(const gfx::Size& frame_size, double frame_rate) { | 205 void WebmMuxer::AddVideoTrack(const gfx::Size& frame_size, double frame_rate) { |
69 DCHECK(thread_checker_.CalledOnValidThread()); | 206 DCHECK(thread_checker_.CalledOnValidThread()); |
70 DCHECK_EQ(track_index_, 0u) << "WebmMuxer can only be initialised once."; | 207 DCHECK_EQ(video_track_index_, 0u) |
mcasas
2015/10/28 00:42:02
nit: expected value goes first, DCHECK_EQ(0u, vide
ajose
2015/10/28 23:43:47
Done.
| |
208 << "WebmMuxer can only be initialized once."; | |
71 | 209 |
72 segment_.Init(this); | 210 video_track_index_ = |
73 segment_.set_mode(mkvmuxer::Segment::kLive); | |
74 segment_.OutputCues(false); | |
75 | |
76 mkvmuxer::SegmentInfo* const info = segment_.GetSegmentInfo(); | |
77 info->set_writing_app("Chrome"); | |
78 info->set_muxing_app("Chrome"); | |
79 | |
80 track_index_ = | |
81 segment_.AddVideoTrack(frame_size.width(), frame_size.height(), 0); | 211 segment_.AddVideoTrack(frame_size.width(), frame_size.height(), 0); |
82 DCHECK_GT(track_index_, 0u); | 212 DCHECK_GT(video_track_index_, 0u); |
83 | 213 |
84 mkvmuxer::VideoTrack* const video_track = | 214 mkvmuxer::VideoTrack* const video_track = |
85 reinterpret_cast<mkvmuxer::VideoTrack*>( | 215 reinterpret_cast<mkvmuxer::VideoTrack*>( |
86 segment_.GetTrackByNumber(track_index_)); | 216 segment_.GetTrackByNumber(video_track_index_)); |
87 DCHECK(video_track); | 217 DCHECK(video_track); |
88 video_track->set_codec_id(use_vp9_ ? mkvmuxer::Tracks::kVp9CodecId | 218 video_track->set_codec_id(use_vp9_ ? mkvmuxer::Tracks::kVp9CodecId |
89 : mkvmuxer::Tracks::kVp8CodecId); | 219 : mkvmuxer::Tracks::kVp8CodecId); |
90 DCHECK_EQ(video_track->crop_right(), 0ull); | 220 DCHECK_EQ(video_track->crop_right(), 0ull); |
91 DCHECK_EQ(video_track->crop_left(), 0ull); | 221 DCHECK_EQ(video_track->crop_left(), 0ull); |
92 DCHECK_EQ(video_track->crop_top(), 0ull); | 222 DCHECK_EQ(video_track->crop_top(), 0ull); |
93 DCHECK_EQ(video_track->crop_bottom(), 0ull); | 223 DCHECK_EQ(video_track->crop_bottom(), 0ull); |
94 | 224 |
95 video_track->set_frame_rate(frame_rate); | 225 video_track->set_frame_rate(frame_rate); |
96 video_track->set_default_duration(base::Time::kNanosecondsPerSecond / | 226 video_track->set_default_duration(base::Time::kNanosecondsPerSecond / |
97 frame_rate); | 227 frame_rate); |
98 // Segment's timestamps should be in milliseconds, DCHECK it. See | 228 // Segment's timestamps should be in milliseconds, DCHECK it. See |
99 // http://www.webmproject.org/docs/container/#muxer-guidelines | 229 // http://www.webmproject.org/docs/container/#muxer-guidelines |
100 DCHECK_EQ(segment_.GetSegmentInfo()->timecode_scale(), 1000000ull); | 230 DCHECK_EQ(segment_.GetSegmentInfo()->timecode_scale(), 1000000ull); |
101 } | 231 } |
102 | 232 |
233 void WebmMuxer::AddAudioTrack(const media::AudioParameters& params) { | |
234 DCHECK(thread_checker_.CalledOnValidThread()); | |
235 DCHECK_EQ(audio_track_index_, 0u) | |
236 << "WebmMuxer audio can only be initialised once."; | |
237 | |
238 audio_track_index_ = | |
239 segment_.AddAudioTrack(params.sample_rate(), params.channels(), 0); | |
240 DCHECK_GT(audio_track_index_, 0u); | |
241 | |
242 mkvmuxer::AudioTrack* const audio_track = | |
243 reinterpret_cast<mkvmuxer::AudioTrack*>( | |
244 segment_.GetTrackByNumber(audio_track_index_)); | |
245 DCHECK(audio_track); | |
246 audio_track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId); | |
247 | |
248 // http://wiki.xiph.org/MatroskaOpus | |
249 audio_track->set_seek_pre_roll(80000000); | |
mcasas
2015/10/28 00:42:02
Nit: let's define a constant for the magic number,
ajose
2015/10/28 23:43:47
Done.
| |
250 | |
251 DCHECK_EQ(audio_track->sample_rate(), params.sample_rate()); | |
252 DCHECK_EQ((int)audio_track->channels(), params.channels()); | |
mcasas
2015/10/28 00:42:02
No C-style casts.
ajose
2015/10/28 23:43:47
Done.
| |
253 | |
254 uint8 opus_header[OPUS_EXTRADATA_SIZE]; | |
255 WriteOpusHeader(params, opus_header); | |
256 | |
257 if (!audio_track->SetCodecPrivate(opus_header, OPUS_EXTRADATA_SIZE)) { | |
258 LOG(ERROR) << __FUNCTION__ << ": failed to set opus header."; | |
259 } | |
mcasas
2015/10/28 00:42:02
nit: no need for {}
ajose
2015/10/28 23:43:47
Done.
| |
260 | |
261 // Segment's timestamps should be in milliseconds, DCHECK it. See | |
262 // http://www.webmproject.org/docs/container/#muxer-guidelines | |
263 DCHECK_EQ(segment_.GetSegmentInfo()->timecode_scale(), 1000000ull); | |
264 } | |
265 | |
103 mkvmuxer::int32 WebmMuxer::Write(const void* buf, mkvmuxer::uint32 len) { | 266 mkvmuxer::int32 WebmMuxer::Write(const void* buf, mkvmuxer::uint32 len) { |
104 DCHECK(thread_checker_.CalledOnValidThread()); | 267 DCHECK(thread_checker_.CalledOnValidThread()); |
105 DCHECK(buf); | 268 DCHECK(buf); |
106 write_data_callback_.Run( | 269 write_data_callback_.Run( |
107 base::StringPiece(reinterpret_cast<const char*>(buf), len)); | 270 base::StringPiece(reinterpret_cast<const char*>(buf), len)); |
108 position_ += len; | 271 position_ += len; |
109 return 0; | 272 return 0; |
110 } | 273 } |
111 | 274 |
112 mkvmuxer::int64 WebmMuxer::Position() const { | 275 mkvmuxer::int64 WebmMuxer::Position() const { |
(...skipping 10 matching lines...) Expand all Loading... | |
123 } | 286 } |
124 | 287 |
125 void WebmMuxer::ElementStartNotify(mkvmuxer::uint64 element_id, | 288 void WebmMuxer::ElementStartNotify(mkvmuxer::uint64 element_id, |
126 mkvmuxer::int64 position) { | 289 mkvmuxer::int64 position) { |
127 // This method gets pinged before items are sent to |write_data_callback_|. | 290 // This method gets pinged before items are sent to |write_data_callback_|. |
128 DCHECK_GE(position, position_.ValueOrDefault(0)) | 291 DCHECK_GE(position, position_.ValueOrDefault(0)) |
129 << "Can't go back in a live WebM stream."; | 292 << "Can't go back in a live WebM stream."; |
130 } | 293 } |
131 | 294 |
132 } // namespace media | 295 } // namespace media |
OLD | NEW |