Index: media/cast/sender/audio_encoder.cc |
diff --git a/media/cast/sender/audio_encoder.cc b/media/cast/sender/audio_encoder.cc |
index 992e519a8e011cd0fc1b91c16a71bc5e035ee73e..273151f2802cabf1c14a8c0d92478b404b765651 100644 |
--- a/media/cast/sender/audio_encoder.cc |
+++ b/media/cast/sender/audio_encoder.cc |
@@ -5,6 +5,8 @@ |
#include "media/cast/sender/audio_encoder.h" |
#include <algorithm> |
+#include <limits> |
+#include <string> |
#include "base/bind.h" |
#include "base/bind_helpers.h" |
@@ -15,7 +17,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 { |
@@ -197,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, |
@@ -301,6 +311,388 @@ class AudioEncoder::OpusImpl : public AudioEncoder::ImplBase { |
DISALLOW_COPY_AND_ASSIGN(OpusImpl); |
}; |
+#endif |
+ |
+#if defined(OS_MACOSX) |
+class AudioEncoder::AppleAacImpl : public AudioEncoder::ImplBase { |
+ // AAC-LC has two access unit sizes (960 and 1024). The Apple encoder only |
+ // supports the latter. |
+ static const int kAccessUnitSamples = 1024; |
+ |
+ // Size of an ADTS header (w/o checksum). See |
+ // http://wiki.multimedia.cx/index.php?title=ADTS |
+ static const int kAdtsHeaderSize = 7; |
+ |
+ public: |
+ AppleAacImpl(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, |
+ kAccessUnitSamples, |
+ callback), |
+ input_buffer_(AudioBus::Create(num_channels, kAccessUnitSamples)), |
+ input_bus_(AudioBus::CreateWrapper(num_channels)), |
+ max_access_unit_size_(0), |
+ output_buffer_(nullptr), |
+ converter_(nullptr), |
+ file_(nullptr), |
+ num_access_units_(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: |
+ virtual ~AppleAacImpl() { 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 non-interleaved |
+ // packed native-endian float samples. Note that in Core Audio, a frame is |
+ // one sample across all channels at a given point in time. When describing |
+ // a non-interleaved samples format, the "per frame" fields mean "per |
+ // channel" or "per stream", with the exception of |mChannelsPerFrame|. For |
+ // uncompressed formats, one packet contains one frame. |
+ 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); |
+ 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 will fully specify the output format and update the |
+ // relevant fields of the structure, which we can now query. |
+ 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; |
+ } |
+ } |
+ |
+#if defined(OS_IOS) |
+ // See the comment next to |can_resume_| for details on resumption. Some |
+ // converters can return kAudioConverterErr_PropertyNotSupported, in which |
+ // case resumption is implicitly supported. This is the only location where |
+ // the implementation modifies |can_resume_|. |
+ 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; |
+ } |
+#endif |
+ |
+ // Figure out the maximum size of an access unit that the encoder can |
+ // produce. |mBytesPerPacket| will be 0 for variable size configurations, |
+ // in which case we must query the value. |
+ uint32_t max_access_unit_size = out_asbd.mBytesPerPacket; |
+ if (max_access_unit_size == 0) { |
+ prop_size = sizeof(max_access_unit_size); |
+ if (AudioConverterGetProperty( |
+ converter_, |
+ kAudioConverterPropertyMaximumOutputPacketSize, |
+ &prop_size, |
+ &max_access_unit_size) != noErr) { |
+ return false; |
+ } |
+ } |
+ |
+ // This is the only location where the implementation modifies |
+ // |max_access_unit_size_|. |
+ const_cast<uint32_t&>(max_access_unit_size_) = max_access_unit_size; |
+ |
+ // Allocate a buffer to store one access unit. This is the only location |
+ // where the implementation modifies |access_unit_buffer_|. |
+ const_cast<scoped_ptr<uint8[]>&>(access_unit_buffer_) |
+ .reset(new uint8[max_access_unit_size]); |
+ |
+ // Initialize the converter ABL. Note that the buffer size has to be set |
+ // before every encode operation, since the field is modified to indicate |
+ // the size of the output data (on input it indicates the buffer capacity). |
+ converter_abl_.mNumberBuffers = 1; |
+ converter_abl_.mBuffers[0].mNumberChannels = num_channels_; |
+ converter_abl_.mBuffers[0].mData = access_unit_buffer_.get(); |
+ |
+ // The "magic cookie" is an encoder state vector required for decoding and |
+ // packetization. It is queried now from |converter_| then set on |file_| |
+ // after initialization. |
+ UInt32 cookie_size; |
+ if (AudioConverterGetPropertyInfo(converter_, |
+ kAudioConverterCompressionMagicCookie, |
+ &cookie_size, |
+ nullptr) != noErr) { |
+ return false; |
+ } |
+ scoped_ptr<uint8[]> cookie_data(new uint8[cookie_size]); |
+ 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; |
+ } |
+ |
+ // Initially the input bus points to the input buffer. See the comment on |
+ // |input_bus_| for more on this optimization. |
+ input_bus_->set_frames(kAccessUnitSamples); |
+ for (int ch = 0; ch < input_buffer_->channels(); ++ch) { |
+ input_bus_->SetChannelData(ch, input_buffer_->channel(ch)); |
+ } |
+ |
+ return true; |
+ } |
+ |
+ void TransferSamplesIntoBuffer(const AudioBus* audio_bus, |
+ int source_offset, |
+ int buffer_fill_offset, |
+ int num_samples) override { |
+ DCHECK_EQ(audio_bus->channels(), input_buffer_->channels()); |
+ |
+ // See the comment on |input_bus_| for more on this optimization. Note that |
+ // we cannot elide the copy if the source offset would result in an |
+ // unaligned pointer. |
+ if (num_samples == kAccessUnitSamples && |
+ source_offset * sizeof(float) % AudioBus::kChannelAlignment == 0) { |
+ DCHECK_EQ(buffer_fill_offset, 0); |
+ for (int ch = 0; ch < audio_bus->channels(); ++ch) { |
+ auto samples = const_cast<float*>(audio_bus->channel(ch)); |
+ input_bus_->SetChannelData(ch, samples + source_offset); |
+ } |
+ return; |
+ } |
+ |
+ // Copy the samples into the input buffer. |
+ DCHECK_EQ(input_bus_->channel(0), input_buffer_->channel(0)); |
+ audio_bus->CopyPartialFramesTo( |
+ source_offset, num_samples, buffer_fill_offset, input_buffer_.get()); |
+ } |
+ |
+ bool EncodeFromFilledBuffer(std::string* out) override { |
+ // Reset the buffer size field to the buffer capacity. |
+ converter_abl_.mBuffers[0].mDataByteSize = max_access_unit_size_; |
+ |
+ // Encode the current input buffer. This is a sychronous call. |
+ OSStatus oserr; |
+ UInt32 io_num_packets = 1; |
+ AudioStreamPacketDescription packet_description; |
+ oserr = AudioConverterFillComplexBuffer(converter_, |
+ &ConverterFillDataCallback, |
+ this, |
+ &io_num_packets, |
+ &converter_abl_, |
+ &packet_description); |
+ if (oserr != noErr || io_num_packets == 0) { |
+ return false; |
+ } |
+ |
+ // Reserve space in the output buffer to write the packet. |
+ out->reserve(packet_description.mDataByteSize + kAdtsHeaderSize); |
+ |
+ // Set the current output buffer and emit an ADTS-wrapped AAC access unit. |
+ // This is a synchronous call. After it returns, reset the output buffer. |
+ output_buffer_ = out; |
+ oserr = AudioFileWritePackets(file_, |
+ false, |
+ converter_abl_.mBuffers[0].mDataByteSize, |
+ &packet_description, |
+ num_access_units_, |
+ &io_num_packets, |
+ converter_abl_.mBuffers[0].mData); |
+ output_buffer_ = nullptr; |
+ if (oserr != noErr || io_num_packets == 0) { |
+ return false; |
+ } |
+ num_access_units_ += io_num_packets; |
+ return true; |
+ } |
+ |
+ // The |AudioConverterFillComplexBuffer| input callback function. Configures |
+ // the provided |AudioBufferList| to alias |input_bus_|. The implementation |
+ // can only supply |kAccessUnitSamples| samples as a result of not copying |
+ // samples or tracking read and write positions. Note that this function is |
+ // called synchronously by |AudioConverterFillComplexBuffer|. |
+ static OSStatus ConverterFillDataCallback( |
+ AudioConverterRef in_converter, |
+ UInt32* io_num_packets, |
+ AudioBufferList* io_data, |
+ AudioStreamPacketDescription** out_packet_desc, |
+ void* in_encoder) { |
+ DCHECK(in_encoder); |
+ auto encoder = reinterpret_cast<AppleAacImpl*>(in_encoder); |
+ auto input_buffer = encoder->input_buffer_.get(); |
+ auto input_bus = encoder->input_bus_.get(); |
+ |
+ DCHECK_EQ(static_cast<int>(*io_num_packets), kAccessUnitSamples); |
+ DCHECK_EQ(io_data->mNumberBuffers, |
+ static_cast<unsigned>(input_bus->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) * *io_num_packets; |
+ io_data->mBuffers[i_buf].mData = input_bus->channel(i_buf); |
+ |
+ // Reset the input bus back to the input buffer. See the comment on |
+ // |input_bus_| for more on this optimization. |
+ input_bus->SetChannelData(i_buf, 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); |
+ DCHECK(in_buffer); |
+ auto encoder = reinterpret_cast<const AppleAacImpl*>(in_encoder); |
+ 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; |
+ } |
+ |
+ // Buffer that holds one AAC access unit worth of samples. The input callback |
+ // function provides samples from this buffer via |input_bus_| to the encoder. |
+ const scoped_ptr<AudioBus> input_buffer_; |
+ |
+ // Wrapper AudioBus used by the input callback function. Normally it wraps |
+ // |input_buffer_|. However, as an optimization when the client submits a |
+ // buffer containing exactly one access unit worth of samples, the bus is |
+ // redirected to the client buffer temporarily. We know that the base |
+ // implementation will call us right after to encode the buffer and thus we |
+ // can eliminate the copy into |input_buffer_|. |
+ const scoped_ptr<AudioBus> input_bus_; |
+ |
+ // A buffer that holds one AAC access unit. Initialized in |Initialize| once |
+ // the maximum access unit size is known. |
+ const scoped_ptr<uint8[]> access_unit_buffer_; |
+ |
+ // The maximum size of an access unit that the encoder can emit. |
+ const uint32_t max_access_unit_size_; |
+ |
+ // A temporary pointer to the current output buffer. Only non-null when |
+ // writing an access unit. Accessed by the AudioFile write callback function. |
+ std::string* output_buffer_; |
+ |
+ // The |AudioConverter| is responsible for AAC encoding. This is a Core Audio |
+ // object, not to be confused with |media::AudioConverter|. |
+ AudioConverterRef converter_; |
+ |
+ // The |AudioFile| is responsible for ADTS packetization. |
+ AudioFileID file_; |
+ |
+ // An |AudioBufferList| passed to the converter to store encoded samples. |
+ AudioBufferList converter_abl_; |
+ |
+ // The number of access units emitted so far by the encoder. |
+ uint64_t num_access_units_; |
+ |
+ // On iOS, audio codecs can be interrupted by other services (such as an |
+ // audio alert or phone call). Depending on the underlying hardware and |
+ // configuration, the codec may have to be thrown away and re-initialized |
+ // after such an interruption. This flag tracks if we can resume or not from |
+ // such an interruption. It is initialized to true, which is the only possible |
+ // value on OS X and on most modern iOS hardware. |
+ // TODO(jfroy): Implement encoder re-initialization after interruption. |
+ // https://crbug.com/424787 |
+ const bool can_resume_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(AppleAacImpl); |
+}; |
+#endif // defined(OS_MACOSX) |
class AudioEncoder::Pcm16Impl : public AudioEncoder::ImplBase { |
public: |
@@ -363,6 +755,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, |
@@ -370,6 +763,16 @@ AudioEncoder::AudioEncoder( |
bitrate, |
frame_encoded_callback); |
break; |
+#endif |
+#if defined(OS_MACOSX) |
+ case CODEC_AUDIO_AAC: |
+ impl_ = new AppleAacImpl(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, |