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