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