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

Side by Side Diff: media/filters/ffmpeg_audio_decoder.cc

Issue 6901135: Rewriting FFmpegAudioDecoder and eliminating DecoderBase. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src
Patch Set: fix run_all_unittests Created 9 years, 3 months 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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/filters/ffmpeg_audio_decoder.h" 5 #include "media/filters/ffmpeg_audio_decoder.h"
6 6
7 #include "base/bind.h"
7 #include "media/base/data_buffer.h" 8 #include "media/base/data_buffer.h"
9 #include "media/base/filter_host.h"
8 #include "media/base/limits.h" 10 #include "media/base/limits.h"
9 #include "media/ffmpeg/ffmpeg_common.h" 11 #include "media/ffmpeg/ffmpeg_common.h"
10 #include "media/filters/ffmpeg_demuxer.h"
11
12 #if !defined(USE_SSE)
13 #if defined(__SSE__) || defined(ARCH_CPU_X86_64) || _M_IX86_FP==1
14 #define USE_SSE 1
15 #else
16 #define USE_SSE 0
17 #endif
18 #endif
19 #if USE_SSE
20 #include <xmmintrin.h>
21 #endif
22 12
23 namespace media { 13 namespace media {
24 14
25 // Size of the decoded audio buffer. 15 // Returns true if the decode result was an error.
26 const size_t FFmpegAudioDecoder::kOutputBufferSize = 16 static bool IsErrorResult(int result, int decoded_size) {
27 AVCODEC_MAX_AUDIO_FRAME_SIZE; 17 return result < 0 ||
18 decoded_size < 0 ||
19 decoded_size > AVCODEC_MAX_AUDIO_FRAME_SIZE;
20 }
21
22 // Returns true if the decode result produced audio samples.
23 static bool ProducedAudioSamples(int decoded_size) {
24 return decoded_size > 0;
25 }
26
27 // Returns true if the decude result was a timestamp packet and not actual audio
28 // data.
29 static bool IsTimestampMarkerPacket(int result, Buffer* input) {
30 // We can get a positive result but no decoded data. This is ok because this
31 // this can be a marker packet that only contains timestamp.
32 return result > 0 && !input->IsEndOfStream() &&
33 input->GetTimestamp() != kNoTimestamp &&
34 input->GetDuration() != kNoTimestamp;
35 }
36
37 // Returns true if the decude result was end of stream.
38 static bool IsEndOfStream(int result, int decoded_size, Buffer* input) {
39 // Three conditions to meet to declare end of stream for this decoder:
40 // 1. FFmpeg didn't read anything.
41 // 2. FFmpeg didn't output anything.
42 // 3. An end of stream buffer is received.
43 return result == 0 && decoded_size == 0 && input->IsEndOfStream();
44 }
45
28 46
29 FFmpegAudioDecoder::FFmpegAudioDecoder(MessageLoop* message_loop) 47 FFmpegAudioDecoder::FFmpegAudioDecoder(MessageLoop* message_loop)
30 : DecoderBase<AudioDecoder, Buffer>(message_loop), 48 : message_loop_(message_loop),
31 codec_context_(NULL), 49 codec_context_(NULL),
32 config_(0, CHANNEL_LAYOUT_NONE, 0), 50 config_(0, CHANNEL_LAYOUT_NONE, 0),
33 estimated_next_timestamp_(kNoTimestamp) { 51 estimated_next_timestamp_(kNoTimestamp),
52 decoded_audio_size_(AVCODEC_MAX_AUDIO_FRAME_SIZE),
53 decoded_audio_(new uint8[decoded_audio_size_]),
54 pending_reads_(0) {
34 } 55 }
35 56
36 FFmpegAudioDecoder::~FFmpegAudioDecoder() { 57 FFmpegAudioDecoder::~FFmpegAudioDecoder() {}
58
59 void FFmpegAudioDecoder::Flush(FilterCallback* callback) {
60 message_loop_->PostTask(
61 FROM_HERE,
62 NewRunnableMethod(this, &FFmpegAudioDecoder::DoFlush, callback));
37 } 63 }
38 64
39 void FFmpegAudioDecoder::DoInitialize(DemuxerStream* demuxer_stream, 65 void FFmpegAudioDecoder::Initialize(
40 bool* success, 66 DemuxerStream* stream,
41 Task* done_cb) { 67 FilterCallback* callback,
42 base::ScopedTaskRunner done_runner(done_cb); 68 StatisticsCallback* stats_callback) {
43 *success = false; 69 // TODO(scherkus): change Initialize() signature to pass |stream| as a
70 // scoped_refptr<>.
71 scoped_refptr<DemuxerStream> ref_stream(stream);
72 message_loop_->PostTask(
73 FROM_HERE,
74 NewRunnableMethod(this, &FFmpegAudioDecoder::DoInitialize,
75 ref_stream, callback, stats_callback));
76 }
44 77
45 AVStream* av_stream = demuxer_stream->GetAVStream(); 78 AudioDecoderConfig FFmpegAudioDecoder::config() {
46 if (!av_stream) { 79 return config_;
47 return; 80 }
48 } 81
82 void FFmpegAudioDecoder::ProduceAudioSamples(scoped_refptr<Buffer> buffer) {
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 Remove buffer from method signature since it is ne
scherkus (not reviewing) 2011/09/16 18:10:13 Good suggestion! Deferring to later CL
83 message_loop_->PostTask(
84 FROM_HERE,
85 NewRunnableMethod(this, &FFmpegAudioDecoder::DoQueueOutput, buffer));
86 }
87
88 void FFmpegAudioDecoder::DoInitialize(
89 DemuxerStream* stream,
90 FilterCallback* callback,
91 StatisticsCallback* stats_callback) {
92 scoped_ptr<FilterCallback> c(callback);
93
94 demuxer_stream_ = stream;
95 AVStream* av_stream = demuxer_stream_->GetAVStream();
96 CHECK(av_stream);
97
98 stats_callback_.reset(stats_callback);
49 99
50 // Grab the AVStream's codec context and make sure we have sensible values. 100 // Grab the AVStream's codec context and make sure we have sensible values.
51 codec_context_ = av_stream->codec; 101 codec_context_ = av_stream->codec;
52 int bps = av_get_bits_per_sample_fmt(codec_context_->sample_fmt); 102 int bps = av_get_bits_per_sample_fmt(codec_context_->sample_fmt);
53 if (codec_context_->channels <= 0 || 103 if (codec_context_->channels <= 0 ||
54 codec_context_->channels > Limits::kMaxChannels || 104 codec_context_->channels > Limits::kMaxChannels ||
55 (codec_context_->channel_layout == 0 && codec_context_->channels > 2) || 105 (codec_context_->channel_layout == 0 && codec_context_->channels > 2) ||
56 bps <= 0 || bps > Limits::kMaxBitsPerSample || 106 bps <= 0 || bps > Limits::kMaxBitsPerSample ||
57 codec_context_->sample_rate <= 0 || 107 codec_context_->sample_rate <= 0 ||
58 codec_context_->sample_rate > Limits::kMaxSampleRate) { 108 codec_context_->sample_rate > Limits::kMaxSampleRate) {
59 DLOG(WARNING) << "Invalid audio stream -" 109 DLOG(ERROR) << "Invalid audio stream -"
60 << " channels: " << codec_context_->channels 110 << " channels: " << codec_context_->channels
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 Indent
scherkus (not reviewing) 2011/09/16 18:10:13 Done.
61 << " channel layout:" << codec_context_->channel_layout 111 << " channel layout:" << codec_context_->channel_layout
62 << " bps: " << bps 112 << " bps: " << bps
63 << " sample rate: " << codec_context_->sample_rate; 113 << " sample rate: " << codec_context_->sample_rate;
114
115 host()->SetError(PIPELINE_ERROR_DECODE);
116 callback->Run();
64 return; 117 return;
65 } 118 }
66 119
67 // Serialize calls to avcodec_open(). 120 // Serialize calls to avcodec_open().
68 AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); 121 AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
69 if (!codec || avcodec_open(codec_context_, codec) < 0) { 122 if (!codec || avcodec_open(codec_context_, codec) < 0) {
123 DLOG(ERROR) << "Could not initialize audio decoder: "
124 << codec_context_->codec_id;
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 Indent
scherkus (not reviewing) 2011/09/16 18:10:13 Done.
125
126 host()->SetError(PIPELINE_ERROR_DECODE);
127 callback->Run();
70 return; 128 return;
71 } 129 }
72 130
131 // Success!
73 config_.bits_per_channel = 132 config_.bits_per_channel =
74 av_get_bits_per_sample_fmt(codec_context_->sample_fmt); 133 av_get_bits_per_sample_fmt(codec_context_->sample_fmt);
75 config_.channel_layout = 134 config_.channel_layout =
76 ChannelLayoutToChromeChannelLayout(codec_context_->channel_layout, 135 ChannelLayoutToChromeChannelLayout(codec_context_->channel_layout,
77 codec_context_->channels); 136 codec_context_->channels);
78 config_.sample_rate = codec_context_->sample_rate; 137 config_.sample_rate = codec_context_->sample_rate;
79 138
80 // Prepare the output buffer. 139 callback->Run();
81 output_buffer_.reset(static_cast<uint8*>(av_malloc(kOutputBufferSize)));
82 if (!output_buffer_.get()) {
83 host()->SetError(PIPELINE_ERROR_OUT_OF_MEMORY);
84 return;
85 }
86 *success = true;
87 } 140 }
88 141
89 AudioDecoderConfig FFmpegAudioDecoder::config() { 142 void FFmpegAudioDecoder::DoFlush(FilterCallback* callback) {
90 return config_; 143 avcodec_flush_buffers(codec_context_);
144 estimated_next_timestamp_ = kNoTimestamp;
145
146 callback->Run();
147 delete callback;
91 } 148 }
92 149
93 void FFmpegAudioDecoder::ProduceAudioSamples(scoped_refptr<Buffer> output) { 150 void FFmpegAudioDecoder::DoQueueOutput(scoped_refptr<Buffer> output) {
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 const& Rename to DoProduceAudioSamples().
94 DecoderBase<AudioDecoder, Buffer>::PostReadTaskHack(output); 151 output_buffers_.push_back(output);
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 Change this to a simple counter member variable si
152 ReadFromDemuxerStream();
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 Do we want to do this if we have reached EOS?
95 } 153 }
96 154
97 void FFmpegAudioDecoder::DoSeek(base::TimeDelta time, Task* done_cb) { 155 void FFmpegAudioDecoder::DoDecodeInput(scoped_refptr<Buffer> input) {
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 const& Rename to DoOnReadComplete() to match Flus
scherkus (not reviewing) 2011/09/16 18:10:13 Done.
98 avcodec_flush_buffers(codec_context_); 156 DCHECK(!output_buffers_.empty());
99 estimated_next_timestamp_ = kNoTimestamp; 157 DCHECK_GT(pending_reads_, 0);
100 done_cb->Run(); 158 pending_reads_--;
101 delete done_cb;
102 }
103
104 // ConvertAudioF32ToS32() converts float audio (F32) to int (S32) in place.
105 // This is a temporary solution.
106 // The purpose of this short term fix is to enable WMApro, which decodes to
107 // float.
108 // The audio driver has been tested by passing the float audio thru.
109 // FFmpeg for ChromeOS only exposes U8, S16 and F32.
110 // To properly expose new audio sample types at the audio driver layer, a enum
111 // should be created to represent all suppported types, including types
112 // for Pepper. FFmpeg should be queried for type and passed along.
113
114 // TODO(fbarchard): Remove this function. Expose all FFmpeg types to driver.
115 // TODO(fbarchard): If this function is kept, move it to audio_util.cc
116
117 #if USE_SSE
118 const __m128 kFloatScaler = _mm_set1_ps( 2147483648.0f );
119 static void FloatToIntSaturate(float* p) {
120 __m128 a = _mm_set1_ps(*p);
121 a = _mm_mul_ss(a, kFloatScaler);
122 *reinterpret_cast<int32*>(p) = _mm_cvtss_si32(a);
123 }
124 #else
125 const float kFloatScaler = 2147483648.0f;
126 const int kMinSample = std::numeric_limits<int32>::min();
127 const int kMaxSample = std::numeric_limits<int32>::max();
128 const float kMinSampleFloat =
129 static_cast<float>(std::numeric_limits<int32>::min());
130 const float kMaxSampleFloat =
131 static_cast<float>(std::numeric_limits<int32>::max());
132 static void FloatToIntSaturate(float* p) {
133 float f = *p * kFloatScaler + 0.5f;
134 int sample;
135 if (f <= kMinSampleFloat) {
136 sample = kMinSample;
137 } else if (f >= kMaxSampleFloat) {
138 sample = kMaxSample;
139 } else {
140 sample = static_cast<int32>(f);
141 }
142 *reinterpret_cast<int32*>(p) = sample;
143 }
144 #endif
145 static void ConvertAudioF32ToS32(void* buffer, int buffer_size) {
146 for (int i = 0; i < buffer_size / 4; ++i) {
147 FloatToIntSaturate(reinterpret_cast<float*>(buffer) + i);
148 }
149 }
150
151 void FFmpegAudioDecoder::DoDecode(Buffer* input) {
152 PipelineStatistics statistics;
153 159
154 // FFmpeg tends to seek Ogg audio streams in the middle of nowhere, giving us 160 // FFmpeg tends to seek Ogg audio streams in the middle of nowhere, giving us
155 // a whole bunch of AV_NOPTS_VALUE packets. Discard them until we find 161 // a whole bunch of AV_NOPTS_VALUE packets. Discard them until we find
156 // something valid. Refer to http://crbug.com/49709 162 // something valid. Refer to http://crbug.com/49709
157 // TODO(hclam): remove this once fixing the issue in FFmpeg.
158 if (input->GetTimestamp() == kNoTimestamp && 163 if (input->GetTimestamp() == kNoTimestamp &&
159 estimated_next_timestamp_ == kNoTimestamp && 164 estimated_next_timestamp_ == kNoTimestamp &&
160 !input->IsEndOfStream()) { 165 !input->IsEndOfStream()) {
161 DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); 166 ReadFromDemuxerStream();
162 return; 167 return;
163 } 168 }
164 169
165 // Due to FFmpeg API changes we no longer have const read-only pointers.
166 AVPacket packet; 170 AVPacket packet;
167 av_init_packet(&packet); 171 av_init_packet(&packet);
168 packet.data = const_cast<uint8*>(input->GetData()); 172 packet.data = const_cast<uint8*>(input->GetData());
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 Mind if we make this a little more explicit for th
scherkus (not reviewing) 2011/09/16 18:10:13 Done.
169 packet.size = input->GetDataSize(); 173 packet.size = input->GetDataSize();
170 174
175 PipelineStatistics statistics;
171 statistics.audio_bytes_decoded = input->GetDataSize(); 176 statistics.audio_bytes_decoded = input->GetDataSize();
172 177
173 int16_t* output_buffer = reinterpret_cast<int16_t*>(output_buffer_.get()); 178 int decoded_audio_size = decoded_audio_size_;
174 int output_buffer_size = kOutputBufferSize; 179 int result = avcodec_decode_audio3(
175 int result = avcodec_decode_audio3(codec_context_, 180 codec_context_, reinterpret_cast<int16_t*>(decoded_audio_.get()),
176 output_buffer, 181 &decoded_audio_size, &packet);
177 &output_buffer_size,
178 &packet);
179 182
180 if (codec_context_->sample_fmt == SAMPLE_FMT_FLT) { 183 if (IsErrorResult(result, decoded_audio_size)) {
181 ConvertAudioF32ToS32(output_buffer, output_buffer_size); 184 DLOG(ERROR) << "Error decoding an audio frame with timestamp: "
182 } 185 << input->GetTimestamp().InMicroseconds() << " us, duration: "
183 186 << input->GetDuration().InMicroseconds() << " us, packet size: "
184 // TODO(ajwong): Consider if kOutputBufferSize should just be an int instead 187 << input->GetDataSize() << " bytes";
185 // of a size_t. 188 ReadFromDemuxerStream();
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 Do we want to issue another read if this is IsEndO
scherkus (not reviewing) 2011/09/16 18:10:13 Would that imply that the audio decoder in questio
186 if (result < 0 ||
187 output_buffer_size < 0 ||
188 static_cast<size_t>(output_buffer_size) > kOutputBufferSize) {
189 VLOG(1) << "Error decoding an audio frame with timestamp: "
190 << input->GetTimestamp().InMicroseconds() << " us, duration: "
191 << input->GetDuration().InMicroseconds() << " us, packet size: "
192 << input->GetDataSize() << " bytes";
193 DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics);
194 return; 189 return;
195 } 190 }
196 191
197 // If we have decoded something, enqueue the result. 192 scoped_refptr<DataBuffer> output;
198 if (output_buffer_size) {
199 DataBuffer* result_buffer = new DataBuffer(output_buffer_size);
200 result_buffer->SetDataSize(output_buffer_size);
201 uint8* data = result_buffer->GetWritableData();
202 memcpy(data, output_buffer, output_buffer_size);
203 193
204 // We don't trust the demuxer, so always calculate the duration based on 194 if (ProducedAudioSamples(decoded_audio_size)) {
205 // the actual number of samples decoded. 195 // Copy the audio samples into an output buffer.
206 base::TimeDelta duration = CalculateDuration(output_buffer_size); 196 output = new DataBuffer(decoded_audio_size);
207 result_buffer->SetDuration(duration); 197 output->SetDataSize(decoded_audio_size);
198 uint8* data = output->GetWritableData();
199 memcpy(data, decoded_audio_.get(), decoded_audio_size);
200 } else if (IsTimestampMarkerPacket(result, input)) {
201 // Nothing else to do here but update our estimation.
202 estimated_next_timestamp_ = input->GetTimestamp() + input->GetDuration();
203 } else if (IsEndOfStream(result, decoded_audio_size, input)) {
204 // Create an end of stream output buffer.
205 output = new DataBuffer(0);
206 output->SetTimestamp(input->GetTimestamp());
207 output->SetDuration(input->GetDuration());
208 }
208 209
209 // Use an estimated timestamp unless the incoming buffer has a valid one. 210 // Decoding finished successfully, update stats and execute callback.
210 if (input->GetTimestamp() == kNoTimestamp) { 211 stats_callback_->Run(statistics);
211 result_buffer->SetTimestamp(estimated_next_timestamp_); 212 if (output) {
213 DCHECK_GT(output_buffers_.size(), 0u);
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 Replace w/ counter check and decrement.
scherkus (not reviewing) 2011/09/16 18:10:13 Future CL
214 output_buffers_.pop_front();
212 215
213 // Keep the estimated timestamp invalid until we get an incoming buffer 216 ConsumeAudioSamples(output);
214 // with a valid timestamp. This can happen during seeks, etc... 217 }
215 if (estimated_next_timestamp_ != kNoTimestamp) { 218 }
216 estimated_next_timestamp_ += duration;
217 }
218 } else {
219 result_buffer->SetTimestamp(input->GetTimestamp());
220 estimated_next_timestamp_ = input->GetTimestamp() + duration;
221 }
222 219
223 EnqueueResult(result_buffer); 220 void FFmpegAudioDecoder::ReadFromDemuxerStream() {
224 DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics); 221 DCHECK(!output_buffers_.empty())
222 << "Reads should only occur if there are output buffers.";
223
224 pending_reads_++;
225 demuxer_stream_->Read(base::Bind(&FFmpegAudioDecoder::OnReadComplete,
226 this));
227 }
228
229 void FFmpegAudioDecoder::OnReadComplete(Buffer* buffer) {
230 // TODO(scherkus): change DemuxerStream::Read() to use scoped_refptr<> for
231 // callback.
232 scoped_refptr<Buffer> ref_buffer(buffer);
233 message_loop_->PostTask(
234 FROM_HERE,
235 NewRunnableMethod(this, &FFmpegAudioDecoder::DoDecodeInput, ref_buffer));
236 }
237
238 void FFmpegAudioDecoder::UpdateDurationAndTimestamp(
239 const Buffer* input,
240 DataBuffer* output) {
241 // Always calculate duration based on the actual number of samples decoded.
242 base::TimeDelta duration = CalculateDuration(output->GetDataSize());
243 output->SetDuration(duration);
244
245 // Use the incoming timestamp if it's valid.
246 if (input->GetTimestamp() != kNoTimestamp) {
247 output->SetTimestamp(input->GetTimestamp());
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 Is this right given that there can be a codec dela
scherkus (not reviewing) 2011/09/16 18:10:13 Good catch! This behaviour is now codified in a un
248 estimated_next_timestamp_ = input->GetTimestamp() + duration;
225 return; 249 return;
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 remove return. Something still doesn't smell righ
226 } 250 }
227 251
228 // We can get a positive result but no decoded data. This is ok because this 252 // Otherwise use an estimated timestamp and attempt to update the estimation
229 // this can be a marker packet that only contains timestamp. In this case we 253 // as long as it's valid.
230 // save the timestamp for later use. 254 output->SetTimestamp(estimated_next_timestamp_);
231 if (result && !input->IsEndOfStream() && 255 if (estimated_next_timestamp_ != kNoTimestamp) {
232 input->GetTimestamp() != kNoTimestamp && 256 estimated_next_timestamp_ += duration;
233 input->GetDuration() != kNoTimestamp) {
234 estimated_next_timestamp_ = input->GetTimestamp() + input->GetDuration();
235 DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics);
236 return;
237 } 257 }
238
239 // Three conditions to meet to declare end of stream for this decoder:
240 // 1. FFmpeg didn't read anything.
241 // 2. FFmpeg didn't output anything.
242 // 3. An end of stream buffer is received.
243 if (result == 0 && output_buffer_size == 0 && input->IsEndOfStream()) {
244 DataBuffer* result_buffer = new DataBuffer(0);
245 result_buffer->SetTimestamp(input->GetTimestamp());
246 result_buffer->SetDuration(input->GetDuration());
247 EnqueueResult(result_buffer);
248 }
249 DecoderBase<AudioDecoder, Buffer>::OnDecodeComplete(statistics);
250 } 258 }
251 259
252 base::TimeDelta FFmpegAudioDecoder::CalculateDuration(size_t size) { 260 base::TimeDelta FFmpegAudioDecoder::CalculateDuration(int size) {
253 int64 denominator = codec_context_->channels * 261 int64 denominator = ChannelLayoutToChannelCount(config_.channel_layout) *
254 av_get_bits_per_sample_fmt(codec_context_->sample_fmt) / 8 * 262 config_.bits_per_channel / 8 * config_.sample_rate;
255 codec_context_->sample_rate;
256 double microseconds = size / 263 double microseconds = size /
acolwell GONE FROM CHROMIUM 2011/09/13 22:09:28 why do we convert to a double and then back to int
scherkus (not reviewing) 2011/09/16 18:10:13 I looked at the original code review but I can't r
257 (denominator / static_cast<double>(base::Time::kMicrosecondsPerSecond)); 264 (denominator / static_cast<double>(base::Time::kMicrosecondsPerSecond));
258 return base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds)); 265 return base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds));
259 } 266 }
260 267
261 } // namespace 268 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698