/* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "webrtc/modules/audio_device/android/opensles_output.h" #include #include "webrtc/modules/audio_device/android/fine_audio_buffer.h" #include "webrtc/modules/audio_device/android/single_rw_fifo.h" #include "webrtc/modules/audio_device/audio_device_buffer.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/thread_wrapper.h" #include "webrtc/system_wrappers/interface/trace.h" using webrtc_opensl::kDefaultSampleRate; using webrtc_opensl::kNumChannels; #define VOID_RETURN #define OPENSL_RETURN_ON_FAILURE(op, ret_val) \ do { \ SLresult err = (op); \ if (err != SL_RESULT_SUCCESS) { \ WEBRTC_TRACE(kTraceError, kTraceAudioDevice, id_, \ "OpenSL error: %d", err); \ assert(false); \ return ret_val; \ } \ } while (0) static const SLEngineOption kOption[] = { { SL_ENGINEOPTION_THREADSAFE, static_cast(SL_BOOLEAN_TRUE) }, }; enum { kNoUnderrun, kUnderrun, }; namespace webrtc { OpenSlesOutput::OpenSlesOutput(const int32_t id) : id_(id), initialized_(false), speaker_initialized_(false), play_initialized_(false), crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), playing_(false), num_fifo_buffers_needed_(0), number_underruns_(0), sles_engine_(NULL), sles_engine_itf_(NULL), sles_player_(NULL), sles_player_itf_(NULL), sles_player_sbq_itf_(NULL), sles_output_mixer_(NULL), audio_buffer_(NULL), active_queue_(0), speaker_sampling_rate_(kDefaultSampleRate), buffer_size_samples_(0), buffer_size_bytes_(0), playout_delay_(0) { } OpenSlesOutput::~OpenSlesOutput() { } int32_t OpenSlesOutput::Init() { assert(!initialized_); // Set up OpenSl engine. OPENSL_RETURN_ON_FAILURE(slCreateEngine(&sles_engine_, 1, kOption, 0, NULL, NULL), -1); OPENSL_RETURN_ON_FAILURE((*sles_engine_)->Realize(sles_engine_, SL_BOOLEAN_FALSE), -1); OPENSL_RETURN_ON_FAILURE((*sles_engine_)->GetInterface(sles_engine_, SL_IID_ENGINE, &sles_engine_itf_), -1); // Set up OpenSl output mix. OPENSL_RETURN_ON_FAILURE( (*sles_engine_itf_)->CreateOutputMix(sles_engine_itf_, &sles_output_mixer_, 0, NULL, NULL), -1); OPENSL_RETURN_ON_FAILURE( (*sles_output_mixer_)->Realize(sles_output_mixer_, SL_BOOLEAN_FALSE), -1); if (!InitSampleRate()) { return -1; } AllocateBuffers(); initialized_ = true; return 0; } int32_t OpenSlesOutput::Terminate() { // It is assumed that the caller has stopped recording before terminating. assert(!playing_); (*sles_output_mixer_)->Destroy(sles_output_mixer_); (*sles_engine_)->Destroy(sles_engine_); initialized_ = false; speaker_initialized_ = false; play_initialized_ = false; return 0; } int32_t OpenSlesOutput::PlayoutDeviceName(uint16_t index, char name[kAdmMaxDeviceNameSize], char guid[kAdmMaxGuidSize]) { assert(index == 0); // Empty strings. name[0] = '\0'; guid[0] = '\0'; return 0; } int32_t OpenSlesOutput::SetPlayoutDevice(uint16_t index) { assert(index == 0); return 0; } int32_t OpenSlesOutput::PlayoutIsAvailable(bool& available) { // NOLINT available = true; return 0; } int32_t OpenSlesOutput::InitPlayout() { assert(initialized_); assert(!play_initialized_); play_initialized_ = true; return 0; } int32_t OpenSlesOutput::StartPlayout() { assert(play_initialized_); assert(!playing_); if (!CreateAudioPlayer()) { return -1; } // Register callback to receive enqueued buffers. OPENSL_RETURN_ON_FAILURE( (*sles_player_sbq_itf_)->RegisterCallback(sles_player_sbq_itf_, PlayerSimpleBufferQueueCallback, this), -1); if (!EnqueueAllBuffers()) { return -1; } { // To prevent the compiler from e.g. optimizing the code to // playing_ = StartCbThreads() which wouldn't have been thread safe. CriticalSectionScoped lock(crit_sect_.get()); playing_ = true; } if (!StartCbThreads()) { playing_ = false; } return 0; } int32_t OpenSlesOutput::StopPlayout() { StopCbThreads(); DestroyAudioPlayer(); return 0; } int32_t OpenSlesOutput::SpeakerIsAvailable(bool& available) { // NOLINT available = true; return 0; } int32_t OpenSlesOutput::InitSpeaker() { assert(!playing_); speaker_initialized_ = true; return 0; } int32_t OpenSlesOutput::SpeakerVolumeIsAvailable(bool& available) { // NOLINT available = true; return 0; } int32_t OpenSlesOutput::SetSpeakerVolume(uint32_t volume) { assert(speaker_initialized_); assert(initialized_); // TODO(hellner): implement. return 0; } int32_t OpenSlesOutput::MaxSpeakerVolume(uint32_t& maxVolume) const { // NOLINT assert(speaker_initialized_); assert(initialized_); // TODO(hellner): implement. maxVolume = 0; return 0; } int32_t OpenSlesOutput::MinSpeakerVolume(uint32_t& minVolume) const { // NOLINT assert(speaker_initialized_); assert(initialized_); // TODO(hellner): implement. minVolume = 0; return 0; } int32_t OpenSlesOutput::SpeakerVolumeStepSize( uint16_t& stepSize) const { // NOLINT assert(speaker_initialized_); stepSize = 1; return 0; } int32_t OpenSlesOutput::SpeakerMuteIsAvailable(bool& available) { // NOLINT available = false; return 0; } int32_t OpenSlesOutput::StereoPlayoutIsAvailable(bool& available) { // NOLINT available = false; return 0; } int32_t OpenSlesOutput::SetStereoPlayout(bool enable) { if (enable) { assert(false); return -1; } return 0; } int32_t OpenSlesOutput::StereoPlayout(bool& enabled) const { // NOLINT enabled = kNumChannels == 2; return 0; } int32_t OpenSlesOutput::PlayoutBuffer( AudioDeviceModule::BufferType& type, // NOLINT uint16_t& sizeMS) const { // NOLINT type = AudioDeviceModule::kAdaptiveBufferSize; sizeMS = playout_delay_; return 0; } int32_t OpenSlesOutput::PlayoutDelay(uint16_t& delayMS) const { // NOLINT delayMS = playout_delay_; return 0; } void OpenSlesOutput::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { audio_buffer_ = audioBuffer; } int32_t OpenSlesOutput::SetLoudspeakerStatus(bool enable) { return 0; } int32_t OpenSlesOutput::GetLoudspeakerStatus(bool& enabled) const { // NOLINT enabled = true; return 0; } int OpenSlesOutput::PlayoutDelayMs() { return playout_delay_; } bool OpenSlesOutput::InitSampleRate() { if (!SetLowLatency()) { speaker_sampling_rate_ = kDefaultSampleRate; // Default is to use 10ms buffers. buffer_size_samples_ = speaker_sampling_rate_ * 10 / 1000; } if (audio_buffer_->SetPlayoutSampleRate(speaker_sampling_rate_) < 0) { return false; } if (audio_buffer_->SetPlayoutChannels(kNumChannels) < 0) { return false; } UpdatePlayoutDelay(); return true; } void OpenSlesOutput::UpdatePlayoutDelay() { // TODO(hellner): Add accurate delay estimate. // On average half the current buffer will have been played out. int outstanding_samples = (TotalBuffersUsed() - 0.5) * buffer_size_samples_; playout_delay_ = outstanding_samples / (speaker_sampling_rate_ / 1000); } bool OpenSlesOutput::SetLowLatency() { if (!audio_manager_.low_latency_supported()) { return false; } buffer_size_samples_ = audio_manager_.native_buffer_size(); assert(buffer_size_samples_ > 0); speaker_sampling_rate_ = audio_manager_.native_output_sample_rate(); assert(speaker_sampling_rate_ > 0); return true; } void OpenSlesOutput::CalculateNumFifoBuffersNeeded() { int number_of_bytes_needed = (speaker_sampling_rate_ * kNumChannels * sizeof(int16_t)) * 10 / 1000; // Ceiling of integer division: 1 + ((x - 1) / y) int buffers_per_10_ms = 1 + ((number_of_bytes_needed - 1) / buffer_size_bytes_); // |num_fifo_buffers_needed_| is a multiple of 10ms of buffered up audio. num_fifo_buffers_needed_ = kNum10MsToBuffer * buffers_per_10_ms; } void OpenSlesOutput::AllocateBuffers() { // Allocate fine buffer to provide frames of the desired size. buffer_size_bytes_ = buffer_size_samples_ * kNumChannels * sizeof(int16_t); fine_buffer_.reset(new FineAudioBuffer(audio_buffer_, buffer_size_bytes_, speaker_sampling_rate_)); // Allocate FIFO to handle passing buffers between processing and OpenSl // threads. CalculateNumFifoBuffersNeeded(); // Needs |buffer_size_bytes_| to be known assert(num_fifo_buffers_needed_ > 0); fifo_.reset(new SingleRwFifo(num_fifo_buffers_needed_)); // Allocate the memory area to be used. play_buf_.reset(new scoped_array[TotalBuffersUsed()]); int required_buffer_size = fine_buffer_->RequiredBufferSizeBytes(); for (int i = 0; i < TotalBuffersUsed(); ++i) { play_buf_[i].reset(new int8_t[required_buffer_size]); } } int OpenSlesOutput::TotalBuffersUsed() const { return num_fifo_buffers_needed_ + kNumOpenSlBuffers; } bool OpenSlesOutput::EnqueueAllBuffers() { active_queue_ = 0; number_underruns_ = 0; for (int i = 0; i < kNumOpenSlBuffers; ++i) { memset(play_buf_[i].get(), 0, buffer_size_bytes_); OPENSL_RETURN_ON_FAILURE( (*sles_player_sbq_itf_)->Enqueue( sles_player_sbq_itf_, reinterpret_cast(play_buf_[i].get()), buffer_size_bytes_), false); } // OpenSL playing has been stopped. I.e. only this thread is touching // |fifo_|. while (fifo_->size() != 0) { // Underrun might have happened when pushing new buffers to the FIFO. fifo_->Pop(); } for (int i = kNumOpenSlBuffers; i < TotalBuffersUsed(); ++i) { memset(play_buf_[i].get(), 0, buffer_size_bytes_); fifo_->Push(play_buf_[i].get()); } return true; } bool OpenSlesOutput::CreateAudioPlayer() { if (!event_.Start()) { assert(false); return false; } SLDataLocator_AndroidSimpleBufferQueue simple_buf_queue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast(kNumOpenSlBuffers) }; SLDataFormat_PCM configuration = webrtc_opensl::CreatePcmConfiguration(speaker_sampling_rate_); SLDataSource audio_source = { &simple_buf_queue, &configuration }; SLDataLocator_OutputMix locator_outputmix; // Setup the data sink structure. locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; locator_outputmix.outputMix = sles_output_mixer_; SLDataSink audio_sink = { &locator_outputmix, NULL }; // Interfaces for streaming audio data, setting volume and Android are needed. // Note the interfaces still need to be initialized. This only tells OpenSl // that the interfaces will be needed at some point. SLInterfaceID ids[kNumInterfaces] = { SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_ANDROIDCONFIGURATION }; SLboolean req[kNumInterfaces] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; OPENSL_RETURN_ON_FAILURE( (*sles_engine_itf_)->CreateAudioPlayer(sles_engine_itf_, &sles_player_, &audio_source, &audio_sink, kNumInterfaces, ids, req), false); // Realize the player in synchronous mode. OPENSL_RETURN_ON_FAILURE((*sles_player_)->Realize(sles_player_, SL_BOOLEAN_FALSE), false); OPENSL_RETURN_ON_FAILURE( (*sles_player_)->GetInterface(sles_player_, SL_IID_PLAY, &sles_player_itf_), false); OPENSL_RETURN_ON_FAILURE( (*sles_player_)->GetInterface(sles_player_, SL_IID_BUFFERQUEUE, &sles_player_sbq_itf_), false); return true; } void OpenSlesOutput::DestroyAudioPlayer() { SLAndroidSimpleBufferQueueItf sles_player_sbq_itf = sles_player_sbq_itf_; { CriticalSectionScoped lock(crit_sect_.get()); sles_player_sbq_itf_ = NULL; sles_player_itf_ = NULL; } event_.Stop(); if (sles_player_sbq_itf) { // Release all buffers currently queued up. OPENSL_RETURN_ON_FAILURE( (*sles_player_sbq_itf)->Clear(sles_player_sbq_itf), VOID_RETURN); } if (sles_player_) { (*sles_player_)->Destroy(sles_player_); sles_player_ = NULL; } } bool OpenSlesOutput::HandleUnderrun(int event_id, int event_msg) { if (!playing_) { return false; } if (event_id == kNoUnderrun) { return false; } WEBRTC_TRACE(kTraceWarning, kTraceAudioDevice, id_, "Audio underrun"); assert(event_id == kUnderrun); assert(event_msg > 0); // Wait for all enqueued buffers to be flushed. if (event_msg != kNumOpenSlBuffers) { return true; } // All buffers have been flushed. Restart the audio from scratch. // No need to check sles_player_itf_ as playing_ would be false before it is // set to NULL. OPENSL_RETURN_ON_FAILURE( (*sles_player_itf_)->SetPlayState(sles_player_itf_, SL_PLAYSTATE_STOPPED), true); EnqueueAllBuffers(); OPENSL_RETURN_ON_FAILURE( (*sles_player_itf_)->SetPlayState(sles_player_itf_, SL_PLAYSTATE_PLAYING), true); return true; } void OpenSlesOutput::PlayerSimpleBufferQueueCallback( SLAndroidSimpleBufferQueueItf sles_player_sbq_itf, void* p_context) { OpenSlesOutput* audio_device = reinterpret_cast(p_context); audio_device->PlayerSimpleBufferQueueCallbackHandler(sles_player_sbq_itf); } void OpenSlesOutput::PlayerSimpleBufferQueueCallbackHandler( SLAndroidSimpleBufferQueueItf sles_player_sbq_itf) { if (fifo_->size() <= 0 || number_underruns_ > 0) { ++number_underruns_; event_.SignalEvent(kUnderrun, number_underruns_); return; } int8_t* audio = fifo_->Pop(); if (audio) OPENSL_RETURN_ON_FAILURE( (*sles_player_sbq_itf)->Enqueue(sles_player_sbq_itf, audio, buffer_size_bytes_), VOID_RETURN); event_.SignalEvent(kNoUnderrun, 0); } bool OpenSlesOutput::StartCbThreads() { play_thread_.reset(ThreadWrapper::CreateThread(CbThread, this, kRealtimePriority, "opensl_play_thread")); assert(play_thread_.get()); OPENSL_RETURN_ON_FAILURE( (*sles_player_itf_)->SetPlayState(sles_player_itf_, SL_PLAYSTATE_PLAYING), false); unsigned int thread_id = 0; if (!play_thread_->Start(thread_id)) { assert(false); return false; } return true; } void OpenSlesOutput::StopCbThreads() { { CriticalSectionScoped lock(crit_sect_.get()); playing_ = false; } if (sles_player_itf_) { OPENSL_RETURN_ON_FAILURE( (*sles_player_itf_)->SetPlayState(sles_player_itf_, SL_PLAYSTATE_STOPPED), VOID_RETURN); } if (play_thread_.get() == NULL) { return; } event_.Stop(); if (play_thread_->Stop()) { play_thread_.reset(); } else { assert(false); } } bool OpenSlesOutput::CbThread(void* context) { return reinterpret_cast(context)->CbThreadImpl(); } bool OpenSlesOutput::CbThreadImpl() { assert(fine_buffer_.get() != NULL); int event_id; int event_msg; // event_ must not be waited on while a lock has been taken. event_.WaitOnEvent(&event_id, &event_msg); CriticalSectionScoped lock(crit_sect_.get()); if (HandleUnderrun(event_id, event_msg)) { return playing_; } // if fifo_ is not full it means next item in memory must be free. while (fifo_->size() < num_fifo_buffers_needed_ && playing_) { int8_t* audio = play_buf_[active_queue_].get(); fine_buffer_->GetBufferData(audio); fifo_->Push(audio); active_queue_ = (active_queue_ + 1) % TotalBuffersUsed(); } return playing_; } } // namespace webrtc