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

Unified Diff: media/cast/sender/audio_encoder.cc

Issue 601413003: [cast] AAC encoder for OS X and iOS based on AudioConverter and AudioFile (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@audio-remove-constants
Patch Set: Modernize to C++11 per the rules at http://chromium-cpp.appspot.com/ Created 6 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 side-by-side diff with in-line comments
Download patch
« media/cast/sender/audio_encoder.h ('K') | « media/cast/sender/audio_encoder.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/cast/sender/audio_encoder.cc
diff --git a/media/cast/sender/audio_encoder.cc b/media/cast/sender/audio_encoder.cc
index 98ab4ddc551277c8690c6b87a75fb2a3ba843a03..ec193a45783359c88e02639d84bf249684491d12 100644
--- a/media/cast/sender/audio_encoder.cc
+++ b/media/cast/sender/audio_encoder.cc
@@ -15,7 +15,14 @@
#include "media/base/audio_bus.h"
#include "media/cast/cast_defines.h"
#include "media/cast/cast_environment.h"
+
+#if !defined(OS_IOS)
#include "third_party/opus/src/include/opus.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include <AudioToolbox/AudioToolbox.h>
+#endif
namespace media {
namespace cast {
@@ -199,6 +206,7 @@ class AudioEncoder::ImplBase
DISALLOW_COPY_AND_ASSIGN(ImplBase);
};
+#if !defined(OS_IOS)
class AudioEncoder::OpusImpl : public AudioEncoder::ImplBase {
public:
OpusImpl(const scoped_refptr<CastEnvironment>& cast_environment,
@@ -290,6 +298,313 @@ class AudioEncoder::OpusImpl : public AudioEncoder::ImplBase {
DISALLOW_COPY_AND_ASSIGN(OpusImpl);
};
+#endif
+
+#if defined(OS_MACOSX)
+class AudioEncoder::AacAudioConverterImpl : public AudioEncoder::ImplBase {
+ public:
+ AacAudioConverterImpl(const scoped_refptr<CastEnvironment>& cast_environment,
+ int num_channels,
+ int sampling_rate,
+ int bitrate,
+ const FrameEncodedCallback& callback)
+ : ImplBase(cast_environment,
+ CODEC_AUDIO_AAC,
+ num_channels,
+ sampling_rate,
+ 1024, /* AudioConverter produces 1024 samples blocks */
miu 2014/09/29 19:54:47 s/AudioConverter/AacImpl/
+ callback),
+ input_buffer_(AudioBus::Create(num_channels, 1024)),
+ output_buffer_(nullptr),
+ converter_(nullptr),
+ file_(nullptr),
+ num_encoded_packets_(0),
+ max_packet_size_(0),
+ can_resume_(true) {
+ if (ImplBase::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED) {
+ return;
+ }
+ if (!Initialize(sampling_rate, bitrate)) {
+ ImplBase::cast_initialization_status_ =
+ STATUS_INVALID_AUDIO_CONFIGURATION;
+ return;
+ }
+ ImplBase::cast_initialization_status_ = STATUS_AUDIO_INITIALIZED;
+ }
+
+ private:
+ // The |AudioConverterFillComplexBuffer| input callback function. Returns the
+ // data that has been copied into the encoder's |buffer_| by the
+ // |TransferSamplesIntoBuffer| method.
miu 2014/09/29 19:54:47 Just a sanity-check: This callback is called synch
jfroy 2014/10/14 01:06:34 Yep. I'll add the comment.
+ static OSStatus ConverterFillDataCallback(
miu 2014/09/29 19:54:47 style nit: static methods need to be declared/defi
+ AudioConverterRef in_converter,
+ UInt32* io_num_packets,
+ AudioBufferList* io_data,
+ AudioStreamPacketDescription** out_packet_desc,
+ void* in_encoder) {
+ DCHECK(io_data);
+ DCHECK(in_encoder);
+ auto encoder = reinterpret_cast<const AacAudioConverterImpl*>(in_encoder);
+
+ // Provide the entire content of the input buffer.
+ const int num_frames = encoder->input_buffer_->frames();
+ *io_num_packets = num_frames;
+
+ DCHECK_EQ(io_data->mNumberBuffers,
+ static_cast<unsigned>(encoder->input_buffer_->channels()));
+ for (int i_buf = 0, end = io_data->mNumberBuffers; i_buf < end; ++i_buf) {
+ io_data->mBuffers[i_buf].mNumberChannels = 1;
+ io_data->mBuffers[i_buf].mDataByteSize = sizeof(float) * num_frames;
+ io_data->mBuffers[i_buf].mData = encoder->input_buffer_->channel(i_buf);
+ }
+ return noErr;
+ }
+
+ // The AudioFile write callback function. Appends the data to the encoder's
+ // current output buffer.
+ static OSStatus FileWriteCallback(void* in_encoder,
+ SInt64 in_position,
+ UInt32 in_size,
+ const void* in_buffer,
+ UInt32* out_size) {
+ DCHECK(in_encoder);
+ auto encoder = reinterpret_cast<const AacAudioConverterImpl*>(in_encoder);
+ DCHECK(in_buffer);
+ auto buffer = reinterpret_cast<const std::string::value_type*>(in_buffer);
+
+ std::string* const output_buffer = encoder->output_buffer_;
+ DCHECK(output_buffer);
+
+ output_buffer->append(buffer, in_size);
+ *out_size = in_size;
+ return noErr;
+ }
+
+ virtual ~AacAudioConverterImpl() { Teardown(); }
+
+ // Destroys the existing audio converter and file, if any.
+ void Teardown() {
+ if (converter_) {
+ AudioConverterDispose(converter_);
+ converter_ = nullptr;
+ }
+ if (file_) {
+ AudioFileClose(file_);
+ file_ = nullptr;
+ }
+ }
+
+ // Initializes the audio converter and file. Calls Teardown to destroy any
+ // existing state. This is so that Initialize() may be called to setup another
+ // converter after a non-resumable interruption.
+ bool Initialize(int sampling_rate, int bitrate) {
+ // Teardown previous audio converter and file.
+ Teardown();
+
+ // Input data comes from AudioBus objects, which carry deinterleaved packed
+ // native-endian float samples.
+ AudioStreamBasicDescription in_asbd;
+ in_asbd.mSampleRate = sampling_rate;
+ in_asbd.mFormatID = kAudioFormatLinearPCM;
+ in_asbd.mFormatFlags =
+ kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
+ in_asbd.mChannelsPerFrame = num_channels_;
+ in_asbd.mBitsPerChannel = sizeof(float) * 8;
+ in_asbd.mFramesPerPacket = 1;
+ in_asbd.mBytesPerPacket = in_asbd.mBytesPerFrame = sizeof(float);
miu 2014/09/29 19:54:47 Somehow, this doesn't seem right. Only 4 bytes pe
jfroy 2014/10/14 01:06:34 In Core Audio, a frame of audio is one sample acro
+ in_asbd.mReserved = 0;
+
+ // Request AAC-LC encoding, with no downmixing or downsampling.
+ AudioStreamBasicDescription out_asbd;
+ memset(&out_asbd, 0, sizeof(AudioStreamBasicDescription));
+ out_asbd.mSampleRate = sampling_rate;
+ out_asbd.mFormatID = kAudioFormatMPEG4AAC;
+ out_asbd.mChannelsPerFrame = num_channels_;
+ UInt32 prop_size = sizeof(out_asbd);
+ if (AudioFormatGetProperty(kAudioFormatProperty_FormatInfo,
+ 0,
+ nullptr,
+ &prop_size,
+ &out_asbd) != noErr) {
+ return false;
+ }
+
+ if (AudioConverterNew(&in_asbd, &out_asbd, &converter_) != noErr) {
+ return false;
+ }
+
+ // The converter is allowed to modify (really fill-in) the output format.
+ // After creation, we can query the final output format.
+ prop_size = sizeof(out_asbd);
+ if (AudioConverterGetProperty(converter_,
+ kAudioConverterCurrentOutputStreamDescription,
+ &prop_size,
+ &out_asbd) != noErr) {
+ return false;
+ }
+
+ // If bitrate is <= 0, allow the encoder to pick a suitable value.
+ // Otherwise, set the bitrate (which can fail if the value is not suitable
+ // or compatible with the output sampling rate or channels).
+ if (bitrate > 0) {
+ prop_size = sizeof(int);
+ if (AudioConverterSetProperty(
+ converter_, kAudioConverterEncodeBitRate, prop_size, &bitrate) !=
+ noErr) {
+ return false;
+ }
+ }
+
+ // See the comment next to |can_resume_| for details on resumption. Some
+ // converters can return kAudioConverterErr_PropertyNotSupported, in which
+ // case resumption is implicitly supported.
+ uint32_t can_resume;
+ prop_size = sizeof(can_resume);
+ OSStatus oserr = AudioConverterGetProperty(
+ converter_,
+ kAudioConverterPropertyCanResumeFromInterruption,
+ &prop_size,
+ &can_resume);
+ if (oserr == noErr) {
+ const_cast<bool&>(can_resume_) = can_resume != 0;
+ }
+
+ // mBytesPerPacket will be 0 for variable packet size configurations, in
+ // which case we must query the maximum possible packet size.
+ const_cast<uint32_t&>(max_packet_size_) = out_asbd.mBytesPerPacket;
+ if (max_packet_size_ == 0) {
+ prop_size = sizeof(max_packet_size_);
+ if (AudioConverterGetProperty(
+ converter_,
+ kAudioConverterPropertyMaximumOutputPacketSize,
+ &prop_size,
+ const_cast<uint32_t*>(&max_packet_size_)) != noErr) {
+ return false;
+ }
+ }
+
+ // This is the only location where we modify the packet buffer.
+ const_cast<scoped_ptr<uint8[]>&>(packet_buffer_)
+ .reset(new uint8[max_packet_size_]);
+
+ // The "magic cookie" is an encoder state vector required for decoding and
+ // packing. We query it from the converter and set it on the file below.
+ UInt32 cookie_size;
+ if (AudioConverterGetPropertyInfo(converter_,
+ kAudioConverterCompressionMagicCookie,
+ &cookie_size,
+ nullptr) != noErr) {
+ return false;
+ }
+ scoped_ptr<int8[]> cookie_data(new int8[cookie_size]);
miu 2014/09/29 19:54:47 Sanity-check: Did you mean uint8 here, or is int8
jfroy 2014/10/14 01:06:34 Their API use void*, which doesn't play well in C+
+ if (AudioConverterGetProperty(converter_,
+ kAudioConverterCompressionMagicCookie,
+ &cookie_size,
+ cookie_data.get()) != noErr) {
+ return false;
+ }
+
+ if (AudioFileInitializeWithCallbacks(this,
+ nullptr,
+ &FileWriteCallback,
+ nullptr,
+ nullptr,
+ kAudioFileAAC_ADTSType,
+ &out_asbd,
+ 0,
+ &file_) != noErr) {
+ return false;
+ }
+
+ if (AudioFileSetProperty(file_,
+ kAudioFilePropertyMagicCookieData,
+ cookie_size,
+ cookie_data.get()) != noErr) {
+ return false;
+ }
+
+ return true;
+ }
+
+ virtual void TransferSamplesIntoBuffer(const AudioBus* audio_bus,
+ int source_offset,
+ int buffer_fill_offset,
+ int num_samples) override {
+ audio_bus->CopyPartialFramesTo(
+ source_offset, num_samples, buffer_fill_offset, input_buffer_.get());
+ }
+
+ virtual bool EncodeFromFilledBuffer(std::string* out) override {
+ AudioBufferList out_abl;
miu 2014/09/29 19:54:47 nit: Can out_abl be initialized once in Initialize
jfroy 2014/10/14 01:06:34 Good idea.
+ out_abl.mNumberBuffers = 1;
+ out_abl.mBuffers[0].mNumberChannels = num_channels_;
+ out_abl.mBuffers[0].mDataByteSize = max_packet_size_;
+ out_abl.mBuffers[0].mData = packet_buffer_.get();
+
+ UInt32 io_num_packets = 1;
+ AudioStreamPacketDescription packet_description;
+ if (AudioConverterFillComplexBuffer(converter_,
+ &ConverterFillDataCallback,
+ this,
+ &io_num_packets,
+ &out_abl,
+ &packet_description) != noErr ||
+ io_num_packets == 0) {
+ return false;
+ }
+
+ // Reserve enough space to write the packet and a basic ADTS header.
+ out->reserve(packet_description.mDataByteSize + 7);
miu 2014/09/29 19:54:47 Where does 7 come from? Can you specify this as s
jfroy 2014/10/14 01:06:34 It's the ADTS header size. I've switched to a cons
+
+ output_buffer_ = out;
+ OSStatus oserr = AudioFileWritePackets(file_,
+ false,
+ out_abl.mBuffers[0].mDataByteSize,
+ &packet_description,
+ num_encoded_packets_,
+ &io_num_packets,
+ out_abl.mBuffers[0].mData);
+ output_buffer_ = nullptr;
+ if (oserr != noErr || io_num_packets == 0) {
+ return false;
+ }
+ num_encoded_packets_ += io_num_packets;
+ return true;
+ }
+
+ // A buffer that holds one encoded packet worth of audio frames. Initialized
+ // during construction.
+ const scoped_ptr<AudioBus> input_buffer_;
+
+ // A buffer that holds one encoded packet. Initialized in |Initialize| once
+ // the maximum packet size is known.
+ const scoped_ptr<uint8[]> packet_buffer_;
+
+ // A temporary pointer to the current output buffer. Only non-null when
+ // writing a packet for use by the AudioFile write callback.
+ std::string* output_buffer_;
+
+ // The AudioConverter is responsible for encoding LPCM frames to AAC packets.
+ AudioConverterRef converter_;
+
+ // The AudioFile is responsible for wrapping AAC packets into ADTS packets.
+ AudioFileID file_;
+
+ // The number of encoded packets emitted so far by the encoder.
+ uint64_t num_encoded_packets_;
+
+ // The maximum packet size that the encoder can emit.
+ const uint32_t max_packet_size_;
+
+ // On iOS, AudioConverter can be interrupted by other services (such as an
+ // audio alert or phone call). Depending on the underlying hardware and
+ // configuration, the converter may have to be thrown away and re-initialized
+ // after such an interruption. This flag tracks if we can resume or not.
+ const bool can_resume_;
miu 2014/09/29 19:54:47 Is this used anywhere?
jfroy 2014/10/14 01:06:34 Not yet but I'll be filing a bug to add support fo
+
+ DISALLOW_COPY_AND_ASSIGN(AacAudioConverterImpl);
+};
+#endif // defined(OS_MACOSX)
class AudioEncoder::Pcm16Impl : public AudioEncoder::ImplBase {
public:
@@ -352,6 +667,7 @@ AudioEncoder::AudioEncoder(
// as all calls to InsertAudio() are by the same thread.
insert_thread_checker_.DetachFromThread();
switch (codec) {
+#if !defined(OS_IOS)
case CODEC_AUDIO_OPUS:
impl_ = new OpusImpl(cast_environment,
num_channels,
@@ -359,6 +675,16 @@ AudioEncoder::AudioEncoder(
bitrate,
frame_encoded_callback);
break;
+#endif
+#if defined(OS_MACOSX)
+ case CODEC_AUDIO_AAC:
+ impl_ = new AacAudioConverterImpl(cast_environment,
+ num_channels,
+ sampling_rate,
+ bitrate,
+ frame_encoded_callback);
+ break;
+#endif // defined(OS_MACOSX)
case CODEC_AUDIO_PCM16:
impl_ = new Pcm16Impl(cast_environment,
num_channels,
« media/cast/sender/audio_encoder.h ('K') | « media/cast/sender/audio_encoder.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698