| 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 |