namespace media {
namespace cast {
-void LogAudioEncodedEvent(CastEnvironment* const cast_environment,
- const base::TimeTicks& recorded_time) {
- // TODO(mikhal): Resolve timestamp calculation for audio.
- base::TimeTicks now = cast_environment->Clock()->NowTicks();
-
- cast_environment->Logging()->InsertFrameEvent(now, kAudioFrameEncoded,
- GetVideoRtpTimestamp(recorded_time), kFrameIdUnknown);
-}
-
// Base class that handles the common problem of feeding one or more AudioBus'
// data into a 10 ms buffer and then, once the buffer is full, encoding the
// signal and emitting an EncodedAudioFrame via the FrameEncodedCallback.
//
// Subclasses complete the implementation by handling the actual encoding
// details.
-class AudioEncoder::ImplBase {
+class AudioEncoder::ImplBase : public base::SupportsWeakPtr<ImplBase> {
public:
ImplBase(CastEnvironment* cast_environment,
- transport::AudioCodec codec, int num_channels, int sampling_rate,
+ transport::AudioCodec codec,
+ int num_channels,
+ int sampling_rate,
const FrameEncodedCallback& callback)
: cast_environment_(cast_environment),
- codec_(codec), num_channels_(num_channels),
+ codec_(codec),
+ num_channels_(num_channels),
samples_per_10ms_(sampling_rate / 100),
callback_(callback),
buffer_fill_end_(0),
- frame_id_(0) {
- CHECK_GT(num_channels_, 0);
- CHECK_GT(samples_per_10ms_, 0);
- CHECK_EQ(sampling_rate % 100, 0);
- CHECK_LE(samples_per_10ms_ * num_channels_,
- transport::EncodedAudioFrame::kMaxNumberOfSamples);
+ frame_id_(0),
+ rtp_timestamp_(0),
+ weak_factory_(this) {
+ DCHECK_GT(num_channels_, 0);
+ DCHECK_GT(samples_per_10ms_, 0);
+ DCHECK_EQ(sampling_rate % 100, 0);
+ DCHECK_LE(samples_per_10ms_ * num_channels_,
+ transport::EncodedAudioFrame::kMaxNumberOfSamples);
+
+ if (num_channels_ <= 0 || samples_per_10ms_ <= 0 ||
+ sampling_rate % 100 != 0 ||
+ samples_per_10ms_ * num_channels_ >
+ transport::EncodedAudioFrame::kMaxNumberOfSamples) {
+ initialization_status_ = STATUS_INVALID_AUDIO_CONFIGURATION;
+ } else {
+ initialization_status_ = STATUS_INITIALIZED;
+ }
}
virtual ~ImplBase() {}
+ CastInitializationStatus InitializationResult() const {
+ return initialization_status_;
+ }
+
+ void LogAudioFrameEvent(uint32 rtp_timestamp,
+ uint32 frame_id,
+ CastLoggingEvent type) {
+ cast_environment_->Logging()->InsertFrameEvent(
+ cast_environment_->Clock()->NowTicks(), type, rtp_timestamp, frame_id);
+ }
+
void EncodeAudio(const AudioBus* audio_bus,
const base::TimeTicks& recorded_time,
const base::Closure& done_callback) {
int src_pos = 0;
+ int packet_count = 0;
while (audio_bus && src_pos < audio_bus->frames()) {
- const int num_samples_to_xfer =
- std::min(samples_per_10ms_ - buffer_fill_end_,
- audio_bus->frames() - src_pos);
+ const int num_samples_to_xfer = std::min(
+ samples_per_10ms_ - buffer_fill_end_, audio_bus->frames() - src_pos);
DCHECK_EQ(audio_bus->channels(), num_channels_);
TransferSamplesIntoBuffer(
audio_bus, src_pos, buffer_fill_end_, num_samples_to_xfer);
buffer_fill_end_ += num_samples_to_xfer;
if (src_pos == audio_bus->frames()) {
- cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
- done_callback);
+ cast_environment_->PostTask(
+ CastEnvironment::MAIN, FROM_HERE, done_callback);
// Note: |audio_bus| is invalid once done_callback is invoked.
audio_bus = NULL;
}
new transport::EncodedAudioFrame());
audio_frame->codec = codec_;
audio_frame->frame_id = frame_id_++;
- audio_frame->samples = samples_per_10ms_;
+ rtp_timestamp_ += samples_per_10ms_;
+ audio_frame->rtp_timestamp = rtp_timestamp_;
+
+ // Update logging.
+ cast_environment_->PostTask(CastEnvironment::MAIN,
+ FROM_HERE,
+ base::Bind(&ImplBase::LogAudioFrameEvent,
+ weak_factory_.GetWeakPtr(),
+ audio_frame->rtp_timestamp,
+ audio_frame->frame_id,
+ kAudioFrameReceived));
+
if (EncodeFromFilledBuffer(&audio_frame->data)) {
+ // Update logging.
+ cast_environment_->PostTask(CastEnvironment::MAIN,
+ FROM_HERE,
+ base::Bind(&ImplBase::LogAudioFrameEvent,
+ weak_factory_.GetWeakPtr(),
+ audio_frame->rtp_timestamp,
+ audio_frame->frame_id,
+ kAudioFrameEncoded));
// Compute an offset to determine the recorded time for the first
// audio sample in the buffer.
const base::TimeDelta buffer_time_offset =
base::TimeDelta::FromMilliseconds(10) / samples_per_10ms_;
// TODO(miu): Consider batching EncodedAudioFrames so we only post a
// at most one task for each call to this method.
- cast_environment_->PostTask(
- CastEnvironment::MAIN, FROM_HERE,
- base::Bind(callback_, base::Passed(&audio_frame),
- recorded_time - buffer_time_offset));
+ // Postpone every packet by 10mS with respect to the previous. Playout
+ // is postponed already by 10mS, and this will better correlate with
+ // the pacer's expectations.
+ //TODO(mikhal): Turn this into a list of packets.
+ // Update the end2end allowed error once this is fixed.
+ cast_environment_->PostDelayedTask(
+ CastEnvironment::MAIN,
+ FROM_HERE,
+ base::Bind(callback_,
+ base::Passed(&audio_frame),
+ recorded_time - buffer_time_offset),
+ base::TimeDelta::FromMilliseconds(packet_count * 10));
+ ++packet_count;
}
buffer_fill_end_ = 0;
}
const int num_channels_;
const int samples_per_10ms_;
const FrameEncodedCallback callback_;
+ CastInitializationStatus initialization_status_;
private:
// In the case where a call to EncodeAudio() cannot completely fill the
// A counter used to label EncodedAudioFrames.
uint32 frame_id_;
+ // For audio, rtp_timestamp is computed as the sum of the audio samples seen
+ // so far.
+ uint32 rtp_timestamp_;
+
+ base::WeakPtrFactory<ImplBase> weak_factory_;
+
private:
DISALLOW_COPY_AND_ASSIGN(ImplBase);
};
class AudioEncoder::OpusImpl : public AudioEncoder::ImplBase {
public:
OpusImpl(CastEnvironment* cast_environment,
- int num_channels, int sampling_rate, int bitrate,
+ int num_channels,
+ int sampling_rate,
+ int bitrate,
const FrameEncodedCallback& callback)
- : ImplBase(cast_environment, transport::kOpus, num_channels,
- sampling_rate, callback),
+ : ImplBase(cast_environment,
+ transport::kOpus,
+ num_channels,
+ sampling_rate,
+ callback),
encoder_memory_(new uint8[opus_encoder_get_size(num_channels)]),
opus_encoder_(reinterpret_cast<OpusEncoder*>(encoder_memory_.get())),
buffer_(new float[num_channels * samples_per_10ms_]) {
- CHECK_EQ(opus_encoder_init(opus_encoder_, sampling_rate, num_channels,
- OPUS_APPLICATION_AUDIO),
- OPUS_OK);
+ if (ImplBase::initialization_status_ != STATUS_INITIALIZED) {
+ return;
+ }
+
+ CHECK_EQ(
+ opus_encoder_init(
+ opus_encoder_, sampling_rate, num_channels, OPUS_APPLICATION_AUDIO),
+ OPUS_OK);
if (bitrate <= 0) {
// Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a
// variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms
virtual bool EncodeFromFilledBuffer(std::string* out) OVERRIDE {
out->resize(kOpusMaxPayloadSize);
- const opus_int32 result = opus_encode_float(
- opus_encoder_, buffer_.get(), samples_per_10ms_,
- reinterpret_cast<uint8*>(&out->at(0)), kOpusMaxPayloadSize);
+ const opus_int32 result =
+ opus_encode_float(opus_encoder_,
+ buffer_.get(),
+ samples_per_10ms_,
+ reinterpret_cast<uint8*>(&out->at(0)),
+ kOpusMaxPayloadSize);
if (result > 1) {
out->resize(result);
return true;
class AudioEncoder::Pcm16Impl : public AudioEncoder::ImplBase {
public:
Pcm16Impl(CastEnvironment* cast_environment,
- int num_channels, int sampling_rate,
+ int num_channels,
+ int sampling_rate,
const FrameEncodedCallback& callback)
- : ImplBase(cast_environment, transport::kPcm16, num_channels,
- sampling_rate, callback),
+ : ImplBase(cast_environment,
+ transport::kPcm16,
+ num_channels,
+ sampling_rate,
+ callback),
buffer_(new int16[num_channels * samples_per_10ms_]) {}
virtual ~Pcm16Impl() {}
int buffer_fill_offset,
int num_samples) OVERRIDE {
audio_bus->ToInterleavedPartial(
- source_offset, num_samples, sizeof(int16),
+ source_offset,
+ num_samples,
+ sizeof(int16),
buffer_.get() + buffer_fill_offset * num_channels_);
}
switch (audio_config.codec) {
case transport::kOpus:
- impl_.reset(new OpusImpl(
- cast_environment, audio_config.channels, audio_config.frequency,
- audio_config.bitrate, frame_encoded_callback));
+ impl_.reset(new OpusImpl(cast_environment,
+ audio_config.channels,
+ audio_config.frequency,
+ audio_config.bitrate,
+ frame_encoded_callback));
break;
case transport::kPcm16:
- impl_.reset(new Pcm16Impl(
- cast_environment, audio_config.channels, audio_config.frequency,
- frame_encoded_callback));
+ impl_.reset(new Pcm16Impl(cast_environment,
+ audio_config.channels,
+ audio_config.frequency,
+ frame_encoded_callback));
break;
default:
NOTREACHED() << "Unsupported or unspecified codec for audio encoder";
AudioEncoder::~AudioEncoder() {}
-void AudioEncoder::InsertAudio(
- const AudioBus* audio_bus,
- const base::TimeTicks& recorded_time,
- const base::Closure& done_callback) {
+CastInitializationStatus AudioEncoder::InitializationResult() const {
+ if (impl_) {
+ return impl_->InitializationResult();
+ }
+ return STATUS_UNSUPPORTED_AUDIO_CODEC;
+}
+
+void AudioEncoder::InsertAudio(const AudioBus* audio_bus,
+ const base::TimeTicks& recorded_time,
+ const base::Closure& done_callback) {
DCHECK(insert_thread_checker_.CalledOnValidThread());
if (!impl_) {
NOTREACHED();
- cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
- done_callback);
+ cast_environment_->PostTask(
+ CastEnvironment::MAIN, FROM_HERE, done_callback);
return;
}
- cast_environment_->PostTask(CastEnvironment::AUDIO_ENCODER, FROM_HERE,
- base::Bind(&AudioEncoder::EncodeAudio, this, audio_bus, recorded_time,
- done_callback));
+ cast_environment_->PostTask(CastEnvironment::AUDIO_ENCODER,
+ FROM_HERE,
+ base::Bind(&AudioEncoder::EncodeAudio,
+ this,
+ audio_bus,
+ recorded_time,
+ done_callback));
}
-void AudioEncoder::EncodeAudio(
- const AudioBus* audio_bus,
- const base::TimeTicks& recorded_time,
- const base::Closure& done_callback) {
+void AudioEncoder::EncodeAudio(const AudioBus* audio_bus,
+ const base::TimeTicks& recorded_time,
+ const base::Closure& done_callback) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::AUDIO_ENCODER));
impl_->EncodeAudio(audio_bus, recorded_time, done_callback);
- cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
- base::Bind(LogAudioEncodedEvent, cast_environment_, recorded_time));
}
} // namespace cast