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, |