Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(280)

Side by Side Diff: media/capture/webm_muxer.cc

Issue 1414793002: Update WebmMuxer for audio component of MediaStream recording. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698