Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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/cast/sender/audio_encoder.h" | 5 #include "media/cast/sender/audio_encoder.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #include "base/bind_helpers.h" | 10 #include "base/bind_helpers.h" |
| 11 #include "base/location.h" | 11 #include "base/location.h" |
| 12 #include "base/stl_util.h" | 12 #include "base/stl_util.h" |
| 13 #include "base/sys_byteorder.h" | 13 #include "base/sys_byteorder.h" |
| 14 #include "base/time/time.h" | 14 #include "base/time/time.h" |
| 15 #include "media/base/audio_bus.h" | 15 #include "media/base/audio_bus.h" |
| 16 #include "media/cast/cast_defines.h" | 16 #include "media/cast/cast_defines.h" |
| 17 #include "media/cast/cast_environment.h" | 17 #include "media/cast/cast_environment.h" |
| 18 #include "third_party/opus/src/include/opus.h" | 18 #include "third_party/opus/src/include/opus.h" |
| 19 | 19 |
| 20 namespace media { | 20 namespace media { |
| 21 namespace cast { | 21 namespace cast { |
| 22 | 22 |
| 23 namespace { | |
| 24 | |
| 25 // The fixed number of audio frames per second and, inversely, the duration of | |
| 26 // one frame's worth of samples. | |
| 27 const int kFramesPerSecond = 100; | |
| 28 const int kFrameDurationMillis = 1000 / kFramesPerSecond; // No remainder! | |
| 29 | |
| 30 // Threshold used to decide whether audio being delivered to the encoder is | |
| 31 // coming in too slow with respect to the capture timestamps. | |
| 32 const int kUnderrunThresholdMillis = 3 * kFrameDurationMillis; | |
| 33 | |
| 34 } // namespace | |
| 35 | |
| 36 | 23 |
| 37 // Base class that handles the common problem of feeding one or more AudioBus' | 24 // Base class that handles the common problem of feeding one or more AudioBus' |
| 38 // data into a buffer and then, once the buffer is full, encoding the signal and | 25 // data into a buffer and then, once the buffer is full, encoding the signal and |
| 39 // emitting an EncodedFrame via the FrameEncodedCallback. | 26 // emitting an EncodedFrame via the FrameEncodedCallback. |
| 40 // | 27 // |
| 41 // Subclasses complete the implementation by handling the actual encoding | 28 // Subclasses complete the implementation by handling the actual encoding |
| 42 // details. | 29 // details. |
| 43 class AudioEncoder::ImplBase | 30 class AudioEncoder::ImplBase |
| 44 : public base::RefCountedThreadSafe<AudioEncoder::ImplBase> { | 31 : public base::RefCountedThreadSafe<AudioEncoder::ImplBase> { |
| 45 public: | 32 public: |
| 46 ImplBase(const scoped_refptr<CastEnvironment>& cast_environment, | 33 ImplBase(const scoped_refptr<CastEnvironment>& cast_environment, |
| 47 Codec codec, | 34 Codec codec, |
| 48 int num_channels, | 35 int num_channels, |
| 49 int sampling_rate, | 36 int sampling_rate, |
| 37 int samples_per_frame, | |
| 50 const FrameEncodedCallback& callback) | 38 const FrameEncodedCallback& callback) |
| 51 : cast_environment_(cast_environment), | 39 : cast_environment_(cast_environment), |
| 52 codec_(codec), | 40 codec_(codec), |
| 53 num_channels_(num_channels), | 41 num_channels_(num_channels), |
| 54 samples_per_frame_(sampling_rate / kFramesPerSecond), | 42 samples_per_frame_(samples_per_frame), |
| 55 callback_(callback), | 43 callback_(callback), |
| 56 cast_initialization_status_(STATUS_AUDIO_UNINITIALIZED), | 44 cast_initialization_status_(STATUS_AUDIO_UNINITIALIZED), |
| 45 frame_duration_milliseconds_( | |
| 46 double(samples_per_frame_) / sampling_rate * 1000), | |
|
miu
2014/09/29 19:13:25
This member should be of type base::TimeDelta, and
jfroy
2014/10/17 21:22:42
Thanks, that is much better.
| |
| 47 underrun_threshold_milliseconds_(frame_duration_milliseconds_ * 3), | |
|
miu
2014/09/29 19:13:25
3 needs to be defined as a constant, but first con
| |
| 57 buffer_fill_end_(0), | 48 buffer_fill_end_(0), |
| 58 frame_id_(0), | 49 frame_id_(0), |
| 59 frame_rtp_timestamp_(0), | 50 frame_rtp_timestamp_(0), |
| 60 samples_dropped_from_buffer_(0) { | 51 samples_dropped_from_buffer_(0) { |
| 61 // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration. | 52 // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration. |
| 62 const int kMaxSamplesTimesChannelsPerFrame = 48 * 2 * 100; | 53 const int kMaxSamplesTimesChannelsPerFrame = 48 * 2 * 100; |
| 63 if (num_channels_ <= 0 || samples_per_frame_ <= 0 || | 54 if (num_channels_ <= 0 || samples_per_frame_ <= 0 || |
| 64 sampling_rate % kFramesPerSecond != 0 || | 55 frame_duration_milliseconds_ < 2 || |
|
miu
2014/09/29 19:13:25
This check needs to be added to OpusImpl's ctor, s
|
miu
2014/09/29 19:13:25
"frame_duration_ > base::TimeDelta()"
jfroy
2014/10/17 21:22:42
I didn't comment on that, but the previous conditi
|
| 65 samples_per_frame_ * num_channels_ > kMaxSamplesTimesChannelsPerFrame) { | 56 samples_per_frame_ * num_channels_ > kMaxSamplesTimesChannelsPerFrame) { |
| 66 cast_initialization_status_ = STATUS_INVALID_AUDIO_CONFIGURATION; | 57 cast_initialization_status_ = STATUS_INVALID_AUDIO_CONFIGURATION; |
| 67 } | 58 } |
| 68 } | 59 } |
| 69 | 60 |
| 70 CastInitializationStatus InitializationResult() const { | 61 CastInitializationStatus InitializationResult() const { |
| 71 return cast_initialization_status_; | 62 return cast_initialization_status_; |
| 72 } | 63 } |
| 73 | 64 |
| 74 int samples_per_frame() const { | 65 int samples_per_frame() const { |
| 75 return samples_per_frame_; | 66 return samples_per_frame_; |
| 76 } | 67 } |
| 77 | 68 |
| 78 void EncodeAudio(scoped_ptr<AudioBus> audio_bus, | 69 void EncodeAudio(scoped_ptr<AudioBus> audio_bus, |
| 79 const base::TimeTicks& recorded_time) { | 70 const base::TimeTicks& recorded_time) { |
| 80 DCHECK_EQ(cast_initialization_status_, STATUS_AUDIO_INITIALIZED); | 71 DCHECK_EQ(cast_initialization_status_, STATUS_AUDIO_INITIALIZED); |
| 81 DCHECK(!recorded_time.is_null()); | 72 DCHECK(!recorded_time.is_null()); |
| 82 | 73 |
| 83 // Determine whether |recorded_time| is consistent with the amount of audio | 74 // Determine whether |recorded_time| is consistent with the amount of audio |
| 84 // data having been processed in the past. Resolve the underrun problem by | 75 // data having been processed in the past. Resolve the underrun problem by |
| 85 // dropping data from the internal buffer and skipping ahead the next | 76 // dropping data from the internal buffer and skipping ahead the next |
| 86 // frame's RTP timestamp by the estimated number of frames missed. On the | 77 // frame's RTP timestamp by the estimated number of frames missed. On the |
| 87 // other hand, don't attempt to resolve overruns: A receiver should | 78 // other hand, don't attempt to resolve overruns: A receiver should |
| 88 // gracefully deal with an excess of audio data. | 79 // gracefully deal with an excess of audio data. |
| 89 const base::TimeDelta frame_duration = | 80 const base::TimeDelta frame_duration = |
| 90 base::TimeDelta::FromMilliseconds(kFrameDurationMillis); | 81 base::TimeDelta::FromMilliseconds(frame_duration_milliseconds_); |
| 91 base::TimeDelta buffer_fill_duration = | 82 base::TimeDelta buffer_fill_duration = |
| 92 buffer_fill_end_ * frame_duration / samples_per_frame_; | 83 buffer_fill_end_ * frame_duration / samples_per_frame_; |
| 93 if (!frame_capture_time_.is_null()) { | 84 if (!frame_capture_time_.is_null()) { |
| 94 const base::TimeDelta amount_ahead_by = | 85 const base::TimeDelta amount_ahead_by = |
| 95 recorded_time - (frame_capture_time_ + buffer_fill_duration); | 86 recorded_time - (frame_capture_time_ + buffer_fill_duration); |
| 96 if (amount_ahead_by > | 87 if (amount_ahead_by > |
| 97 base::TimeDelta::FromMilliseconds(kUnderrunThresholdMillis)) { | 88 base::TimeDelta::FromMilliseconds(underrun_threshold_milliseconds_)) { |
| 98 samples_dropped_from_buffer_ += buffer_fill_end_; | 89 samples_dropped_from_buffer_ += buffer_fill_end_; |
| 99 buffer_fill_end_ = 0; | 90 buffer_fill_end_ = 0; |
| 100 buffer_fill_duration = base::TimeDelta(); | 91 buffer_fill_duration = base::TimeDelta(); |
| 101 const int64 num_frames_missed = amount_ahead_by / | 92 const int64 num_frames_missed = |
|
miu
2014/09/29 19:13:25
This should be:
const int64 num_frames_missed =
jfroy
2014/10/17 21:22:42
Makes sense.
| |
| 102 base::TimeDelta::FromMilliseconds(kFrameDurationMillis); | 93 amount_ahead_by / |
| 94 base::TimeDelta::FromMilliseconds(frame_duration_milliseconds_); | |
| 103 frame_rtp_timestamp_ += | 95 frame_rtp_timestamp_ += |
| 104 static_cast<uint32>(num_frames_missed * samples_per_frame_); | 96 static_cast<uint32>(num_frames_missed * samples_per_frame_); |
| 105 DVLOG(1) << "Skipping RTP timestamp ahead to account for " | 97 DVLOG(1) << "Skipping RTP timestamp ahead to account for " |
| 106 << num_frames_missed * samples_per_frame_ | 98 << num_frames_missed * samples_per_frame_ |
| 107 << " samples' worth of underrun."; | 99 << " samples' worth of underrun."; |
| 108 } | 100 } |
| 109 } | 101 } |
| 110 frame_capture_time_ = recorded_time - buffer_fill_duration; | 102 frame_capture_time_ = recorded_time - buffer_fill_duration; |
| 111 | 103 |
| 112 // Encode all audio in |audio_bus| into zero or more frames. | 104 // Encode all audio in |audio_bus| into zero or more frames. |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 162 const scoped_refptr<CastEnvironment> cast_environment_; | 154 const scoped_refptr<CastEnvironment> cast_environment_; |
| 163 const Codec codec_; | 155 const Codec codec_; |
| 164 const int num_channels_; | 156 const int num_channels_; |
| 165 const int samples_per_frame_; | 157 const int samples_per_frame_; |
| 166 const FrameEncodedCallback callback_; | 158 const FrameEncodedCallback callback_; |
| 167 | 159 |
| 168 // Subclass' ctor is expected to set this to STATUS_AUDIO_INITIALIZED. | 160 // Subclass' ctor is expected to set this to STATUS_AUDIO_INITIALIZED. |
| 169 CastInitializationStatus cast_initialization_status_; | 161 CastInitializationStatus cast_initialization_status_; |
| 170 | 162 |
| 171 private: | 163 private: |
| 164 // The duration of one frame (or packet) of encoded audio samples. Derived | |
| 165 // from samples_per_frame_ and sampling_rate. | |
| 166 const int frame_duration_milliseconds_; | |
| 167 | |
| 168 // Threshold used to decide whether audio being delivered to the encoder is | |
| 169 // coming in too slow with respect to the capture timestamps. Derived from | |
| 170 // frame_duration_milliseconds_. | |
| 171 const int underrun_threshold_milliseconds_; | |
| 172 | |
| 172 // In the case where a call to EncodeAudio() cannot completely fill the | 173 // In the case where a call to EncodeAudio() cannot completely fill the |
| 173 // buffer, this points to the position at which to populate data in a later | 174 // buffer, this points to the position at which to populate data in a later |
| 174 // call. | 175 // call. |
| 175 int buffer_fill_end_; | 176 int buffer_fill_end_; |
| 176 | 177 |
| 177 // A counter used to label EncodedFrames. | 178 // A counter used to label EncodedFrames. |
| 178 uint32 frame_id_; | 179 uint32 frame_id_; |
| 179 | 180 |
| 180 // The RTP timestamp for the next frame of encoded audio. This is defined as | 181 // The RTP timestamp for the next frame of encoded audio. This is defined as |
| 181 // the number of audio samples encoded so far, plus the estimated number of | 182 // the number of audio samples encoded so far, plus the estimated number of |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 202 public: | 203 public: |
| 203 OpusImpl(const scoped_refptr<CastEnvironment>& cast_environment, | 204 OpusImpl(const scoped_refptr<CastEnvironment>& cast_environment, |
| 204 int num_channels, | 205 int num_channels, |
| 205 int sampling_rate, | 206 int sampling_rate, |
| 206 int bitrate, | 207 int bitrate, |
| 207 const FrameEncodedCallback& callback) | 208 const FrameEncodedCallback& callback) |
| 208 : ImplBase(cast_environment, | 209 : ImplBase(cast_environment, |
| 209 CODEC_AUDIO_OPUS, | 210 CODEC_AUDIO_OPUS, |
| 210 num_channels, | 211 num_channels, |
| 211 sampling_rate, | 212 sampling_rate, |
| 213 sampling_rate / 100, /* 10 ms frames */ | |
|
miu
2014/09/29 19:13:25
style: 100 needs to be represented by an integer c
jfroy
2014/10/17 21:22:42
OK, and apply that constant to the PCM impl as wel
| |
| 212 callback), | 214 callback), |
| 213 encoder_memory_(new uint8[opus_encoder_get_size(num_channels)]), | 215 encoder_memory_(new uint8[opus_encoder_get_size(num_channels)]), |
| 214 opus_encoder_(reinterpret_cast<OpusEncoder*>(encoder_memory_.get())), | 216 opus_encoder_(reinterpret_cast<OpusEncoder*>(encoder_memory_.get())), |
| 215 buffer_(new float[num_channels * samples_per_frame_]) { | 217 buffer_(new float[num_channels * samples_per_frame_]) { |
| 216 if (ImplBase::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED) | 218 if (ImplBase::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED) |
| 217 return; | 219 return; |
| 218 if (opus_encoder_init(opus_encoder_, | 220 if (opus_encoder_init(opus_encoder_, |
| 219 sampling_rate, | 221 sampling_rate, |
| 220 num_channels, | 222 num_channels, |
| 221 OPUS_APPLICATION_AUDIO) != OPUS_OK) { | 223 OPUS_APPLICATION_AUDIO) != OPUS_OK) { |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 292 class AudioEncoder::Pcm16Impl : public AudioEncoder::ImplBase { | 294 class AudioEncoder::Pcm16Impl : public AudioEncoder::ImplBase { |
| 293 public: | 295 public: |
| 294 Pcm16Impl(const scoped_refptr<CastEnvironment>& cast_environment, | 296 Pcm16Impl(const scoped_refptr<CastEnvironment>& cast_environment, |
| 295 int num_channels, | 297 int num_channels, |
| 296 int sampling_rate, | 298 int sampling_rate, |
| 297 const FrameEncodedCallback& callback) | 299 const FrameEncodedCallback& callback) |
| 298 : ImplBase(cast_environment, | 300 : ImplBase(cast_environment, |
| 299 CODEC_AUDIO_PCM16, | 301 CODEC_AUDIO_PCM16, |
| 300 num_channels, | 302 num_channels, |
| 301 sampling_rate, | 303 sampling_rate, |
| 304 sampling_rate / 100, /* 10 ms frames */ | |
| 302 callback), | 305 callback), |
| 303 buffer_(new int16[num_channels * samples_per_frame_]) { | 306 buffer_(new int16[num_channels * samples_per_frame_]) { |
| 304 if (ImplBase::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED) | 307 if (ImplBase::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED) |
| 305 return; | 308 return; |
| 306 cast_initialization_status_ = STATUS_AUDIO_INITIALIZED; | 309 cast_initialization_status_ = STATUS_AUDIO_INITIALIZED; |
| 307 } | 310 } |
| 308 | 311 |
| 309 private: | 312 private: |
| 310 virtual ~Pcm16Impl() {} | 313 virtual ~Pcm16Impl() {} |
| 311 | 314 |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 398 cast_environment_->PostTask(CastEnvironment::AUDIO, | 401 cast_environment_->PostTask(CastEnvironment::AUDIO, |
| 399 FROM_HERE, | 402 FROM_HERE, |
| 400 base::Bind(&AudioEncoder::ImplBase::EncodeAudio, | 403 base::Bind(&AudioEncoder::ImplBase::EncodeAudio, |
| 401 impl_, | 404 impl_, |
| 402 base::Passed(&audio_bus), | 405 base::Passed(&audio_bus), |
| 403 recorded_time)); | 406 recorded_time)); |
| 404 } | 407 } |
| 405 | 408 |
| 406 } // namespace cast | 409 } // namespace cast |
| 407 } // namespace media | 410 } // namespace media |
| OLD | NEW |