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