0

[Chromecast] Move CastAudioDecoder out to chromecast/media/cma/decoder

BUG= internal b/25701982

Review URL: https://codereview.chromium.org/1494713002

Cr-Commit-Position: refs/heads/master@{#362848}
This commit is contained in:
kmackay
2015-12-02 17:37:43 -08:00
committed by Commit bot
parent 0d3e17ae21
commit a95f19786b
6 changed files with 440 additions and 0 deletions

@ -0,0 +1,18 @@
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
source_set("decoder") {
sources = [
"cast_audio_decoder.h",
"cast_audio_decoder_android.cc",
"cast_audio_decoder_linux.cc",
]
deps = [
"//base",
"//chromecast/media/cma/base",
"//media",
"//media:shared_memory_support",
]
}

@ -0,0 +1,4 @@
include_rules = [
"+chromecast/media/cma/base",
"+media/filters",
]

@ -0,0 +1,76 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMECAST_MEDIA_CMA_DECODER_CAST_AUDIO_DECODER_H_
#define CHROMECAST_MEDIA_CMA_DECODER_CAST_AUDIO_DECODER_H_
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
namespace base {
class SingleThreadTaskRunner;
} // namespace base
namespace chromecast {
namespace media {
struct AudioConfig;
class DecoderBufferBase;
// Audio decoder interface.
class CastAudioDecoder {
public:
enum Status {
kDecodeOk,
kDecodeError,
};
enum OutputFormat {
kOutputSigned16, // Output signed 16-bit interleaved samples.
kOutputPlanarFloat, // Output planar float samples.
};
// The callback that is called when the decoder initialization is complete.
// |success| is true if initialization was successful; if |success| is false
// then the CastAudioDecoder instance is unusable and should be destroyed.
typedef base::Callback<void(bool success)> InitializedCallback;
typedef base::Callback<void(
Status status,
const scoped_refptr<media::DecoderBufferBase>& decoded)> DecodeCallback;
// Creates a CastAudioDecoder instance for the given |config|. Decoding must
// occur on the same thread as |task_runner|. Returns an empty scoped_ptr if
// the decoder could not be created.
static scoped_ptr<CastAudioDecoder> Create(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const media::AudioConfig& config,
OutputFormat output_format,
const InitializedCallback& initialized_callback);
// Given a CastAudioDecoder::OutputFormat, return the size of each sample in
// that OutputFormat in bytes.
static int OutputFormatSizeInBytes(CastAudioDecoder::OutputFormat format);
CastAudioDecoder() {}
virtual ~CastAudioDecoder() {}
// Converts encoded data to the |output_format|. Must be called on the same
// thread as |task_runner|. Decoded data will be passed to |decode_callback|.
// It is OK to call Decode before the |initialized_callback| has been called;
// those buffers will be queued until initialization completes, at which point
// they will be decoded in order (if initialization was successful), or
// ignored if initialization failed.
// It is OK to pass an end-of-stream DecoderBuffer as |data|.
// Returns |false| if the decoder is not ready yet.
virtual bool Decode(const scoped_refptr<media::DecoderBufferBase>& data,
const DecodeCallback& decode_callback) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(CastAudioDecoder);
};
} // namespace media
} // namespace chromecast
#endif // CHROMECAST_MEDIA_CMA_DECODER_CAST_AUDIO_DECODER_H_

@ -0,0 +1,35 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/media/cma/decoder/cast_audio_decoder.h"
#include "base/logging.h"
namespace chromecast {
namespace media {
// static
scoped_ptr<CastAudioDecoder> CastAudioDecoder::Create(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const media::AudioConfig& config,
OutputFormat output_format,
const InitializedCallback& initialized_callback) {
return scoped_ptr<CastAudioDecoder>();
}
// static
int CastAudioDecoder::OutputFormatSizeInBytes(
CastAudioDecoder::OutputFormat format) {
switch (format) {
case CastAudioDecoder::OutputFormat::kOutputSigned16:
return 2;
case CastAudioDecoder::OutputFormat::kOutputPlanarFloat:
return 4;
}
NOTREACHED();
return 1;
}
} // namespace media
} // namespace chromecast

@ -0,0 +1,293 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/media/cma/decoder/cast_audio_decoder.h"
#include <limits>
#include <queue>
#include <vector>
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/single_thread_task_runner.h"
#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
#include "chromecast/media/cma/base/decoder_buffer_base.h"
#include "chromecast/media/cma/base/decoder_config_adapter.h"
#include "media/base/audio_buffer.h"
#include "media/base/audio_bus.h"
#include "media/base/cdm_context.h"
#include "media/base/channel_layout.h"
#include "media/base/channel_mixer.h"
#include "media/base/decoder_buffer.h"
#include "media/base/sample_format.h"
#include "media/filters/ffmpeg_audio_decoder.h"
#include "media/filters/opus_audio_decoder.h"
namespace chromecast {
namespace media {
namespace {
const int kOpusSamplingRate = 48000;
const uint8 kFakeOpusExtraData[19] = {
'O', 'p', 'u', 's', 'H', 'e', 'a', 'd', // offset 0, OpusHead
0, // offset 8, version
2, // offset 9, channels
0, 0, // offset 10, skip
static_cast<uint8>(kOpusSamplingRate & 0xFF), // offset 12, LE
static_cast<uint8>((kOpusSamplingRate >> 8) & 0xFF),
static_cast<uint8>((kOpusSamplingRate >> 16) & 0xFF),
static_cast<uint8>((kOpusSamplingRate >> 24) & 0xFF),
0, 0, // offset 16, gain
0, // offset 18, stereo mapping
};
const int kOutputChannelCount = 2; // Always output stereo audio.
const int kMaxChannelInput = 2;
class CastAudioDecoderImpl : public CastAudioDecoder {
public:
CastAudioDecoderImpl(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const InitializedCallback& initialized_callback,
OutputFormat output_format)
: task_runner_(task_runner),
initialized_callback_(initialized_callback),
output_format_(output_format),
initialized_(false),
decode_pending_(false),
weak_factory_(this) {}
~CastAudioDecoderImpl() override {}
void Initialize(const media::AudioConfig& config) {
DCHECK(!initialized_);
DCHECK_LE(config_.channel_number, kMaxChannelInput);
config_ = config;
if (config_.channel_number == 1) {
// If the input is mono, create a ChannelMixer to convert mono to stereo.
// TODO(kmackay) Support other channel format conversions?
mixer_.reset(new ::media::ChannelMixer(::media::CHANNEL_LAYOUT_MONO,
::media::CHANNEL_LAYOUT_STEREO));
}
base::WeakPtr<CastAudioDecoderImpl> self = weak_factory_.GetWeakPtr();
if (config.codec == media::kCodecOpus) {
// Insert fake extradata to make OpusAudioDecoder work with v2mirroring.
if (config_.extra_data.empty() &&
config_.samples_per_second == kOpusSamplingRate &&
config_.channel_number == 2)
config_.extra_data.assign(
kFakeOpusExtraData,
kFakeOpusExtraData + sizeof(kFakeOpusExtraData));
decoder_.reset(new ::media::OpusAudioDecoder(task_runner_));
} else {
decoder_.reset(new ::media::FFmpegAudioDecoder(
task_runner_, make_scoped_refptr(new ::media::MediaLog())));
}
decoder_->Initialize(
media::DecoderConfigAdapter::ToMediaAudioDecoderConfig(config_),
::media::SetCdmReadyCB(),
base::Bind(&CastAudioDecoderImpl::OnInitialized, self),
base::Bind(&CastAudioDecoderImpl::OnDecoderOutput, self));
// Unfortunately there is no result from decoder_->Initialize() until later
// (the pipeline status callback is posted to the task runner).
}
// CastAudioDecoder implementation:
bool Decode(const scoped_refptr<media::DecoderBufferBase>& data,
const DecodeCallback& decode_callback) override {
DCHECK(!decode_callback.is_null());
DCHECK(task_runner_->BelongsToCurrentThread());
if (!initialized_ || decode_pending_) {
decode_queue_.push(std::make_pair(data, decode_callback));
} else {
DecodeNow(data, decode_callback);
}
return true;
}
private:
typedef std::pair<scoped_refptr<media::DecoderBufferBase>, DecodeCallback>
DecodeBufferCallbackPair;
void DecodeNow(const scoped_refptr<media::DecoderBufferBase>& data,
const DecodeCallback& decode_callback) {
if (data->end_of_stream()) {
// Post the task to ensure that |decode_callback| is not called from
// within a call to Decode().
task_runner_->PostTask(FROM_HERE,
base::Bind(decode_callback, kDecodeOk, data));
return;
}
// FFmpegAudioDecoder requires a timestamp to be set.
base::TimeDelta timestamp =
base::TimeDelta::FromMicroseconds(data->timestamp());
if (timestamp == ::media::kNoTimestamp())
data->set_timestamp(base::TimeDelta());
decode_pending_ = true;
decoder_->Decode(data->ToMediaBuffer(),
base::Bind(&CastAudioDecoderImpl::OnDecodeStatus,
weak_factory_.GetWeakPtr(),
timestamp,
decode_callback));
}
void OnInitialized(bool success) {
DCHECK(!initialized_);
LOG_IF(ERROR, !success) << "Failed to initialize FFmpegAudioDecoder";
if (success)
initialized_ = true;
if (success && !decode_queue_.empty()) {
const auto& d = decode_queue_.front();
DecodeNow(d.first, d.second);
decode_queue_.pop();
}
if (!initialized_callback_.is_null())
initialized_callback_.Run(initialized_);
}
void OnDecodeStatus(base::TimeDelta buffer_timestamp,
const DecodeCallback& decode_callback,
::media::AudioDecoder::Status status) {
Status result_status = kDecodeOk;
scoped_refptr<media::DecoderBufferBase> decoded;
if (status == ::media::AudioDecoder::kOk && !decoded_chunks_.empty()) {
decoded = ConvertDecoded();
} else {
if (status != ::media::AudioDecoder::kOk)
result_status = kDecodeError;
decoded = new media::DecoderBufferAdapter(config_.id,
new ::media::DecoderBuffer(0));
}
decoded_chunks_.clear();
decoded->set_timestamp(buffer_timestamp);
decode_callback.Run(result_status, decoded);
// Do not reset decode_pending_ to false until after the callback has
// finished running because the callback may call Decode().
decode_pending_ = false;
if (decode_queue_.empty())
return;
const auto& d = decode_queue_.front();
// Calling DecodeNow() here does not result in a loop, because
// OnDecodeStatus() is always called asynchronously (guaranteed by the
// AudioDecoder interface).
DecodeNow(d.first, d.second);
decode_queue_.pop();
}
void OnDecoderOutput(const scoped_refptr<::media::AudioBuffer>& decoded) {
decoded_chunks_.push_back(decoded);
}
scoped_refptr<media::DecoderBufferBase> ConvertDecoded() {
DCHECK(!decoded_chunks_.empty());
int num_frames = 0;
for (auto& chunk : decoded_chunks_)
num_frames += chunk->frame_count();
// Copy decoded data into an AudioBus for conversion.
scoped_ptr<::media::AudioBus> decoded =
::media::AudioBus::Create(config_.channel_number, num_frames);
int bus_frame_offset = 0;
for (auto& chunk : decoded_chunks_) {
chunk->ReadFrames(
chunk->frame_count(), 0, bus_frame_offset, decoded.get());
bus_frame_offset += chunk->frame_count();
}
if (mixer_) {
// Convert to stereo if necessary.
scoped_ptr<::media::AudioBus> converted_to_stereo =
::media::AudioBus::Create(kOutputChannelCount, num_frames);
mixer_->Transform(decoded.get(), converted_to_stereo.get());
decoded.swap(converted_to_stereo);
}
// Convert to the desired output format.
return FinishConversion(decoded.get());
}
scoped_refptr<media::DecoderBufferBase> FinishConversion(
::media::AudioBus* bus) {
DCHECK_EQ(kOutputChannelCount, bus->channels());
int size = bus->frames() * kOutputChannelCount *
OutputFormatSizeInBytes(output_format_);
scoped_refptr<::media::DecoderBuffer> result(
new ::media::DecoderBuffer(size));
if (output_format_ == kOutputSigned16) {
bus->ToInterleaved(bus->frames(),
OutputFormatSizeInBytes(output_format_),
result->writable_data());
} else if (output_format_ == kOutputPlanarFloat) {
// Data in an AudioBus is already in planar float format; just copy each
// channel into the result buffer in order.
float* ptr = reinterpret_cast<float*>(result->writable_data());
for (int c = 0; c < bus->channels(); ++c) {
memcpy(ptr, bus->channel(c), bus->frames() * sizeof(float));
ptr += bus->frames();
}
} else {
NOTREACHED();
}
result->set_duration(base::TimeDelta::FromMicroseconds(
bus->frames() * base::Time::kMicrosecondsPerSecond /
config_.samples_per_second));
return make_scoped_refptr(
new media::DecoderBufferAdapter(config_.id, result));
}
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
InitializedCallback initialized_callback_;
OutputFormat output_format_;
media::AudioConfig config_;
scoped_ptr<::media::AudioDecoder> decoder_;
std::queue<DecodeBufferCallbackPair> decode_queue_;
bool initialized_;
scoped_ptr<::media::ChannelMixer> mixer_;
bool decode_pending_;
std::vector<scoped_refptr<::media::AudioBuffer>> decoded_chunks_;
base::WeakPtrFactory<CastAudioDecoderImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(CastAudioDecoderImpl);
};
} // namespace
// static
scoped_ptr<CastAudioDecoder> CastAudioDecoder::Create(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const media::AudioConfig& config,
OutputFormat output_format,
const InitializedCallback& initialized_callback) {
scoped_ptr<CastAudioDecoderImpl> decoder(new CastAudioDecoderImpl(
task_runner, initialized_callback, output_format));
decoder->Initialize(config);
return decoder.Pass();
}
// static
int CastAudioDecoder::OutputFormatSizeInBytes(
CastAudioDecoder::OutputFormat format) {
switch (format) {
case CastAudioDecoder::OutputFormat::kOutputSigned16:
return 2;
case CastAudioDecoder::OutputFormat::kOutputPlanarFloat:
return 4;
}
NOTREACHED();
return 1;
}
} // namespace media
} // namespace chromecast

@ -146,6 +146,20 @@
'cma/backend/video_decoder_default.h',
],
},
{
'target_name': 'cma_decoder',
'type': '<(component)',
'dependencies': [
'cma_base',
'../../base/base.gyp:base',
'../../media/media.gyp:media',
],
'sources': [
'cma/decoder/cast_audio_decoder.h',
'cma/decoder/cast_audio_decoder_android.cc',
'cma/decoder/cast_audio_decoder_linux.cc',
],
},
{
'target_name': 'cma_ipc',
'type': '<(component)',