Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,216 @@
/*
* Copyright (c) 2018 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 "modules/audio_device/android/aaudio_player.h"
#include <memory>
#include "api/array_view.h"
#include "api/task_queue/task_queue_base.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/fine_audio_buffer.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
AAudioPlayer::AAudioPlayer(AudioManager* audio_manager)
: main_thread_(TaskQueueBase::Current()),
aaudio_(audio_manager, AAUDIO_DIRECTION_OUTPUT, this) {
RTC_LOG(LS_INFO) << "ctor";
thread_checker_aaudio_.Detach();
}
AAudioPlayer::~AAudioPlayer() {
RTC_LOG(LS_INFO) << "dtor";
RTC_DCHECK_RUN_ON(&main_thread_checker_);
Terminate();
RTC_LOG(LS_INFO) << "#detected underruns: " << underrun_count_;
}
int AAudioPlayer::Init() {
RTC_LOG(LS_INFO) << "Init";
RTC_DCHECK_RUN_ON(&main_thread_checker_);
if (aaudio_.audio_parameters().channels() == 2) {
RTC_DLOG(LS_WARNING) << "Stereo mode is enabled";
}
return 0;
}
int AAudioPlayer::Terminate() {
RTC_LOG(LS_INFO) << "Terminate";
RTC_DCHECK_RUN_ON(&main_thread_checker_);
StopPlayout();
return 0;
}
int AAudioPlayer::InitPlayout() {
RTC_LOG(LS_INFO) << "InitPlayout";
RTC_DCHECK_RUN_ON(&main_thread_checker_);
RTC_DCHECK(!initialized_);
RTC_DCHECK(!playing_);
if (!aaudio_.Init()) {
return -1;
}
initialized_ = true;
return 0;
}
bool AAudioPlayer::PlayoutIsInitialized() const {
RTC_DCHECK_RUN_ON(&main_thread_checker_);
return initialized_;
}
int AAudioPlayer::StartPlayout() {
RTC_LOG(LS_INFO) << "StartPlayout";
RTC_DCHECK_RUN_ON(&main_thread_checker_);
RTC_DCHECK(!playing_);
if (!initialized_) {
RTC_DLOG(LS_WARNING)
<< "Playout can not start since InitPlayout must succeed first";
return 0;
}
if (fine_audio_buffer_) {
fine_audio_buffer_->ResetPlayout();
}
if (!aaudio_.Start()) {
return -1;
}
underrun_count_ = aaudio_.xrun_count();
first_data_callback_ = true;
playing_ = true;
return 0;
}
int AAudioPlayer::StopPlayout() {
RTC_LOG(LS_INFO) << "StopPlayout";
RTC_DCHECK_RUN_ON(&main_thread_checker_);
if (!initialized_ || !playing_) {
return 0;
}
if (!aaudio_.Stop()) {
RTC_LOG(LS_ERROR) << "StopPlayout failed";
return -1;
}
thread_checker_aaudio_.Detach();
initialized_ = false;
playing_ = false;
return 0;
}
bool AAudioPlayer::Playing() const {
RTC_DCHECK_RUN_ON(&main_thread_checker_);
return playing_;
}
void AAudioPlayer::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
RTC_DLOG(LS_INFO) << "AttachAudioBuffer";
RTC_DCHECK_RUN_ON(&main_thread_checker_);
audio_device_buffer_ = audioBuffer;
const AudioParameters audio_parameters = aaudio_.audio_parameters();
audio_device_buffer_->SetPlayoutSampleRate(audio_parameters.sample_rate());
audio_device_buffer_->SetPlayoutChannels(audio_parameters.channels());
RTC_CHECK(audio_device_buffer_);
// Create a modified audio buffer class which allows us to ask for any number
// of samples (and not only multiple of 10ms) to match the optimal buffer
// size per callback used by AAudio.
fine_audio_buffer_ = std::make_unique<FineAudioBuffer>(audio_device_buffer_);
}
int AAudioPlayer::SpeakerVolumeIsAvailable(bool& available) {
available = false;
return 0;
}
void AAudioPlayer::OnErrorCallback(aaudio_result_t error) {
RTC_LOG(LS_ERROR) << "OnErrorCallback: " << AAudio_convertResultToText(error);
// TODO(henrika): investigate if we can use a thread checker here. Initial
// tests shows that this callback can sometimes be called on a unique thread
// but according to the documentation it should be on the same thread as the
// data callback.
// RTC_DCHECK_RUN_ON(&thread_checker_aaudio_);
if (aaudio_.stream_state() == AAUDIO_STREAM_STATE_DISCONNECTED) {
// The stream is disconnected and any attempt to use it will return
// AAUDIO_ERROR_DISCONNECTED.
RTC_LOG(LS_WARNING) << "Output stream disconnected";
// AAudio documentation states: "You should not close or reopen the stream
// from the callback, use another thread instead". A message is therefore
// sent to the main thread to do the restart operation.
RTC_DCHECK(main_thread_);
main_thread_->PostTask([this] { HandleStreamDisconnected(); });
}
}
aaudio_data_callback_result_t AAudioPlayer::OnDataCallback(void* audio_data,
int32_t num_frames) {
RTC_DCHECK_RUN_ON(&thread_checker_aaudio_);
// Log device id in first data callback to ensure that a valid device is
// utilized.
if (first_data_callback_) {
RTC_LOG(LS_INFO) << "--- First output data callback: "
"device id="
<< aaudio_.device_id();
first_data_callback_ = false;
}
// Check if the underrun count has increased. If it has, increase the buffer
// size by adding the size of a burst. It will reduce the risk of underruns
// at the expense of an increased latency.
// TODO(henrika): enable possibility to disable and/or tune the algorithm.
const int32_t underrun_count = aaudio_.xrun_count();
if (underrun_count > underrun_count_) {
RTC_LOG(LS_ERROR) << "Underrun detected: " << underrun_count;
underrun_count_ = underrun_count;
aaudio_.IncreaseOutputBufferSize();
}
// Estimate latency between writing an audio frame to the output stream and
// the time that same frame is played out on the output audio device.
latency_millis_ = aaudio_.EstimateLatencyMillis();
// TODO(henrika): use for development only.
if (aaudio_.frames_written() % (1000 * aaudio_.frames_per_burst()) == 0) {
RTC_DLOG(LS_INFO) << "output latency: " << latency_millis_
<< ", num_frames: " << num_frames;
}
// Read audio data from the WebRTC source using the FineAudioBuffer object
// and write that data into `audio_data` to be played out by AAudio.
// Prime output with zeros during a short initial phase to avoid distortion.
// TODO(henrika): do more work to figure out of if the initial forced silence
// period is really needed.
if (aaudio_.frames_written() < 50 * aaudio_.frames_per_burst()) {
const size_t num_bytes =
sizeof(int16_t) * aaudio_.samples_per_frame() * num_frames;
memset(audio_data, 0, num_bytes);
} else {
fine_audio_buffer_->GetPlayoutData(
rtc::MakeArrayView(static_cast<int16_t*>(audio_data),
aaudio_.samples_per_frame() * num_frames),
static_cast<int>(latency_millis_ + 0.5));
}
// TODO(henrika): possibly add trace here to be included in systrace.
// See https://developer.android.com/studio/profile/systrace-commandline.html.
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
void AAudioPlayer::HandleStreamDisconnected() {
RTC_DCHECK_RUN_ON(&main_thread_checker_);
RTC_DLOG(LS_INFO) << "HandleStreamDisconnected";
if (!initialized_ || !playing_) {
return;
}
// Perform a restart by first closing the disconnected stream and then start
// a new stream; this time using the new (preferred) audio output device.
StopPlayout();
InitPlayout();
StartPlayout();
}
} // namespace webrtc

View file

@ -0,0 +1,141 @@
/*
* Copyright (c) 2018 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_AAUDIO_PLAYER_H_
#define MODULES_AUDIO_DEVICE_ANDROID_AAUDIO_PLAYER_H_
#include <aaudio/AAudio.h>
#include <memory>
#include "api/sequence_checker.h"
#include "api/task_queue/task_queue_base.h"
#include "modules/audio_device/android/aaudio_wrapper.h"
#include "modules/audio_device/include/audio_device_defines.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
class AudioDeviceBuffer;
class FineAudioBuffer;
class AudioManager;
// Implements low-latency 16-bit mono PCM audio output support for Android
// using the C based AAudio API.
//
// An instance must be created and destroyed on one and the same thread.
// All public methods must also be called on the same thread. A thread checker
// will DCHECK if any method is called on an invalid thread. Audio buffers
// are requested on a dedicated high-priority thread owned by AAudio.
//
// The existing design forces the user to call InitPlayout() after StopPlayout()
// to be able to call StartPlayout() again. This is in line with how the Java-
// based implementation works.
//
// An audio stream can be disconnected, e.g. when an audio device is removed.
// This implementation will restart the audio stream using the new preferred
// device if such an event happens.
//
// Also supports automatic buffer-size adjustment based on underrun detections
// where the internal AAudio buffer can be increased when needed. It will
// reduce the risk of underruns (~glitches) at the expense of an increased
// latency.
class AAudioPlayer final : public AAudioObserverInterface {
public:
explicit AAudioPlayer(AudioManager* audio_manager);
~AAudioPlayer();
int Init();
int Terminate();
int InitPlayout();
bool PlayoutIsInitialized() const;
int StartPlayout();
int StopPlayout();
bool Playing() const;
void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer);
// Not implemented in AAudio.
int SpeakerVolumeIsAvailable(bool& available); // NOLINT
int SetSpeakerVolume(uint32_t volume) { return -1; }
int SpeakerVolume(uint32_t& volume) const { return -1; } // NOLINT
int MaxSpeakerVolume(uint32_t& maxVolume) const { return -1; } // NOLINT
int MinSpeakerVolume(uint32_t& minVolume) const { return -1; } // NOLINT
protected:
// AAudioObserverInterface implementation.
// For an output stream, this function should render and write `num_frames`
// of data in the streams current data format to the `audio_data` buffer.
// Called on a real-time thread owned by AAudio.
aaudio_data_callback_result_t OnDataCallback(void* audio_data,
int32_t num_frames) override;
// AAudio calls this functions if any error occurs on a callback thread.
// Called on a real-time thread owned by AAudio.
void OnErrorCallback(aaudio_result_t error) override;
private:
// Closes the existing stream and starts a new stream.
void HandleStreamDisconnected();
// Ensures that methods are called from the same thread as this object is
// created on.
SequenceChecker main_thread_checker_;
// Stores thread ID in first call to AAudioPlayer::OnDataCallback from a
// real-time thread owned by AAudio. Detached during construction of this
// object.
SequenceChecker thread_checker_aaudio_;
// The task queue on which this object is created on.
TaskQueueBase* main_thread_;
// Wraps all AAudio resources. Contains an output stream using the default
// output audio device. Can be accessed on both the main thread and the
// real-time thread owned by AAudio. See separate AAudio documentation about
// thread safety.
AAudioWrapper aaudio_;
// FineAudioBuffer takes an AudioDeviceBuffer which delivers audio data
// in chunks of 10ms. It then allows for this data to be pulled in
// a finer or coarser granularity. I.e. interacting with this class instead
// of directly with the AudioDeviceBuffer one can ask for any number of
// audio data samples.
// Example: native buffer size can be 192 audio frames at 48kHz sample rate.
// WebRTC will provide 480 audio frames per 10ms but AAudio asks for 192
// in each callback (once every 4th ms). This class can then ask for 192 and
// the FineAudioBuffer will ask WebRTC for new data approximately only every
// second callback and also cache non-utilized audio.
std::unique_ptr<FineAudioBuffer> fine_audio_buffer_;
// Counts number of detected underrun events reported by AAudio.
int32_t underrun_count_ = 0;
// True only for the first data callback in each audio session.
bool first_data_callback_ = true;
// Raw pointer handle provided to us in AttachAudioBuffer(). Owned by the
// AudioDeviceModuleImpl class and set by AudioDeviceModule::Create().
AudioDeviceBuffer* audio_device_buffer_ RTC_GUARDED_BY(main_thread_checker_) =
nullptr;
bool initialized_ RTC_GUARDED_BY(main_thread_checker_) = false;
bool playing_ RTC_GUARDED_BY(main_thread_checker_) = false;
// Estimated latency between writing an audio frame to the output stream and
// the time that same frame is played out on the output audio device.
double latency_millis_ RTC_GUARDED_BY(thread_checker_aaudio_) = 0;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_AAUDIO_PLAYER_H_

View file

@ -0,0 +1,205 @@
/*
* Copyright (c) 2018 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 "modules/audio_device/android/aaudio_recorder.h"
#include <memory>
#include "api/array_view.h"
#include "api/task_queue/task_queue_base.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/fine_audio_buffer.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
AAudioRecorder::AAudioRecorder(AudioManager* audio_manager)
: main_thread_(TaskQueueBase::Current()),
aaudio_(audio_manager, AAUDIO_DIRECTION_INPUT, this) {
RTC_LOG(LS_INFO) << "ctor";
thread_checker_aaudio_.Detach();
}
AAudioRecorder::~AAudioRecorder() {
RTC_LOG(LS_INFO) << "dtor";
RTC_DCHECK(thread_checker_.IsCurrent());
Terminate();
RTC_LOG(LS_INFO) << "detected owerflows: " << overflow_count_;
}
int AAudioRecorder::Init() {
RTC_LOG(LS_INFO) << "Init";
RTC_DCHECK(thread_checker_.IsCurrent());
if (aaudio_.audio_parameters().channels() == 2) {
RTC_DLOG(LS_WARNING) << "Stereo mode is enabled";
}
return 0;
}
int AAudioRecorder::Terminate() {
RTC_LOG(LS_INFO) << "Terminate";
RTC_DCHECK(thread_checker_.IsCurrent());
StopRecording();
return 0;
}
int AAudioRecorder::InitRecording() {
RTC_LOG(LS_INFO) << "InitRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!initialized_);
RTC_DCHECK(!recording_);
if (!aaudio_.Init()) {
return -1;
}
initialized_ = true;
return 0;
}
int AAudioRecorder::StartRecording() {
RTC_LOG(LS_INFO) << "StartRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(initialized_);
RTC_DCHECK(!recording_);
if (fine_audio_buffer_) {
fine_audio_buffer_->ResetPlayout();
}
if (!aaudio_.Start()) {
return -1;
}
overflow_count_ = aaudio_.xrun_count();
first_data_callback_ = true;
recording_ = true;
return 0;
}
int AAudioRecorder::StopRecording() {
RTC_LOG(LS_INFO) << "StopRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
if (!initialized_ || !recording_) {
return 0;
}
if (!aaudio_.Stop()) {
return -1;
}
thread_checker_aaudio_.Detach();
initialized_ = false;
recording_ = false;
return 0;
}
void AAudioRecorder::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
RTC_LOG(LS_INFO) << "AttachAudioBuffer";
RTC_DCHECK(thread_checker_.IsCurrent());
audio_device_buffer_ = audioBuffer;
const AudioParameters audio_parameters = aaudio_.audio_parameters();
audio_device_buffer_->SetRecordingSampleRate(audio_parameters.sample_rate());
audio_device_buffer_->SetRecordingChannels(audio_parameters.channels());
RTC_CHECK(audio_device_buffer_);
// Create a modified audio buffer class which allows us to deliver any number
// of samples (and not only multiples of 10ms which WebRTC uses) to match the
// native AAudio buffer size.
fine_audio_buffer_ = std::make_unique<FineAudioBuffer>(audio_device_buffer_);
}
int AAudioRecorder::EnableBuiltInAEC(bool enable) {
RTC_LOG(LS_INFO) << "EnableBuiltInAEC: " << enable;
RTC_LOG(LS_ERROR) << "Not implemented";
return -1;
}
int AAudioRecorder::EnableBuiltInAGC(bool enable) {
RTC_LOG(LS_INFO) << "EnableBuiltInAGC: " << enable;
RTC_LOG(LS_ERROR) << "Not implemented";
return -1;
}
int AAudioRecorder::EnableBuiltInNS(bool enable) {
RTC_LOG(LS_INFO) << "EnableBuiltInNS: " << enable;
RTC_LOG(LS_ERROR) << "Not implemented";
return -1;
}
void AAudioRecorder::OnErrorCallback(aaudio_result_t error) {
RTC_LOG(LS_ERROR) << "OnErrorCallback: " << AAudio_convertResultToText(error);
// RTC_DCHECK(thread_checker_aaudio_.IsCurrent());
if (aaudio_.stream_state() == AAUDIO_STREAM_STATE_DISCONNECTED) {
// The stream is disconnected and any attempt to use it will return
// AAUDIO_ERROR_DISCONNECTED..
RTC_LOG(LS_WARNING) << "Input stream disconnected => restart is required";
// AAudio documentation states: "You should not close or reopen the stream
// from the callback, use another thread instead". A message is therefore
// sent to the main thread to do the restart operation.
RTC_DCHECK(main_thread_);
main_thread_->PostTask([this] { HandleStreamDisconnected(); });
}
}
// Read and process `num_frames` of data from the `audio_data` buffer.
// TODO(henrika): possibly add trace here to be included in systrace.
// See https://developer.android.com/studio/profile/systrace-commandline.html.
aaudio_data_callback_result_t AAudioRecorder::OnDataCallback(
void* audio_data,
int32_t num_frames) {
// TODO(henrika): figure out why we sometimes hit this one.
// RTC_DCHECK(thread_checker_aaudio_.IsCurrent());
// RTC_LOG(LS_INFO) << "OnDataCallback: " << num_frames;
// Drain the input buffer at first callback to ensure that it does not
// contain any old data. Will also ensure that the lowest possible latency
// is obtained.
if (first_data_callback_) {
RTC_LOG(LS_INFO) << "--- First input data callback: "
"device id="
<< aaudio_.device_id();
aaudio_.ClearInputStream(audio_data, num_frames);
first_data_callback_ = false;
}
// Check if the overflow counter has increased and if so log a warning.
// TODO(henrika): possible add UMA stat or capacity extension.
const int32_t overflow_count = aaudio_.xrun_count();
if (overflow_count > overflow_count_) {
RTC_LOG(LS_ERROR) << "Overflow detected: " << overflow_count;
overflow_count_ = overflow_count;
}
// Estimated time between an audio frame was recorded by the input device and
// it can read on the input stream.
latency_millis_ = aaudio_.EstimateLatencyMillis();
// TODO(henrika): use for development only.
if (aaudio_.frames_read() % (1000 * aaudio_.frames_per_burst()) == 0) {
RTC_DLOG(LS_INFO) << "input latency: " << latency_millis_
<< ", num_frames: " << num_frames;
}
// Copy recorded audio in `audio_data` to the WebRTC sink using the
// FineAudioBuffer object.
fine_audio_buffer_->DeliverRecordedData(
rtc::MakeArrayView(static_cast<const int16_t*>(audio_data),
aaudio_.samples_per_frame() * num_frames),
static_cast<int>(latency_millis_ + 0.5));
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
void AAudioRecorder::HandleStreamDisconnected() {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_LOG(LS_INFO) << "HandleStreamDisconnected";
if (!initialized_ || !recording_) {
return;
}
// Perform a restart by first closing the disconnected stream and then start
// a new stream; this time using the new (preferred) audio input device.
// TODO(henrika): resolve issue where a one restart attempt leads to a long
// sequence of new calls to OnErrorCallback().
// See b/73148976 for details.
StopRecording();
InitRecording();
StartRecording();
}
} // namespace webrtc

View file

@ -0,0 +1,124 @@
/*
* Copyright (c) 2018 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_AAUDIO_RECORDER_H_
#define MODULES_AUDIO_DEVICE_ANDROID_AAUDIO_RECORDER_H_
#include <aaudio/AAudio.h>
#include <memory>
#include "api/sequence_checker.h"
#include "api/task_queue/task_queue_base.h"
#include "modules/audio_device/android/aaudio_wrapper.h"
#include "modules/audio_device/include/audio_device_defines.h"
namespace webrtc {
class AudioDeviceBuffer;
class FineAudioBuffer;
class AudioManager;
// Implements low-latency 16-bit mono PCM audio input support for Android
// using the C based AAudio API.
//
// An instance must be created and destroyed on one and the same thread.
// All public methods must also be called on the same thread. A thread checker
// will RTC_DCHECK if any method is called on an invalid thread. Audio buffers
// are delivered on a dedicated high-priority thread owned by AAudio.
//
// The existing design forces the user to call InitRecording() after
// StopRecording() to be able to call StartRecording() again. This is in line
// with how the Java- based implementation works.
//
// TODO(henrika): add comments about device changes and adaptive buffer
// management.
class AAudioRecorder : public AAudioObserverInterface {
public:
explicit AAudioRecorder(AudioManager* audio_manager);
~AAudioRecorder();
int Init();
int Terminate();
int InitRecording();
bool RecordingIsInitialized() const { return initialized_; }
int StartRecording();
int StopRecording();
bool Recording() const { return recording_; }
void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer);
double latency_millis() const { return latency_millis_; }
// TODO(henrika): add support using AAudio APIs when available.
int EnableBuiltInAEC(bool enable);
int EnableBuiltInAGC(bool enable);
int EnableBuiltInNS(bool enable);
protected:
// AAudioObserverInterface implementation.
// For an input stream, this function should read `num_frames` of recorded
// data, in the stream's current data format, from the `audio_data` buffer.
// Called on a real-time thread owned by AAudio.
aaudio_data_callback_result_t OnDataCallback(void* audio_data,
int32_t num_frames) override;
// AAudio calls this function if any error occurs on a callback thread.
// Called on a real-time thread owned by AAudio.
void OnErrorCallback(aaudio_result_t error) override;
private:
// Closes the existing stream and starts a new stream.
void HandleStreamDisconnected();
// Ensures that methods are called from the same thread as this object is
// created on.
SequenceChecker thread_checker_;
// Stores thread ID in first call to AAudioPlayer::OnDataCallback from a
// real-time thread owned by AAudio. Detached during construction of this
// object.
SequenceChecker thread_checker_aaudio_;
// The thread on which this object is created on.
TaskQueueBase* main_thread_;
// Wraps all AAudio resources. Contains an input stream using the default
// input audio device.
AAudioWrapper aaudio_;
// Raw pointer handle provided to us in AttachAudioBuffer(). Owned by the
// AudioDeviceModuleImpl class and called by AudioDeviceModule::Create().
AudioDeviceBuffer* audio_device_buffer_ = nullptr;
bool initialized_ = false;
bool recording_ = false;
// Consumes audio of native buffer size and feeds the WebRTC layer with 10ms
// chunks of audio.
std::unique_ptr<FineAudioBuffer> fine_audio_buffer_;
// Counts number of detected overflow events reported by AAudio.
int32_t overflow_count_ = 0;
// Estimated time between an audio frame was recorded by the input device and
// it can read on the input stream.
double latency_millis_ = 0;
// True only for the first data callback in each audio session.
bool first_data_callback_ = true;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_AAUDIO_RECORDER_H_

View file

@ -0,0 +1,499 @@
/*
* Copyright (c) 2018 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 "modules/audio_device/android/aaudio_wrapper.h"
#include "modules/audio_device/android/audio_manager.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/time_utils.h"
#define LOG_ON_ERROR(op) \
do { \
aaudio_result_t result = (op); \
if (result != AAUDIO_OK) { \
RTC_LOG(LS_ERROR) << #op << ": " << AAudio_convertResultToText(result); \
} \
} while (0)
#define RETURN_ON_ERROR(op, ...) \
do { \
aaudio_result_t result = (op); \
if (result != AAUDIO_OK) { \
RTC_LOG(LS_ERROR) << #op << ": " << AAudio_convertResultToText(result); \
return __VA_ARGS__; \
} \
} while (0)
namespace webrtc {
namespace {
const char* DirectionToString(aaudio_direction_t direction) {
switch (direction) {
case AAUDIO_DIRECTION_OUTPUT:
return "OUTPUT";
case AAUDIO_DIRECTION_INPUT:
return "INPUT";
default:
return "UNKNOWN";
}
}
const char* SharingModeToString(aaudio_sharing_mode_t mode) {
switch (mode) {
case AAUDIO_SHARING_MODE_EXCLUSIVE:
return "EXCLUSIVE";
case AAUDIO_SHARING_MODE_SHARED:
return "SHARED";
default:
return "UNKNOWN";
}
}
const char* PerformanceModeToString(aaudio_performance_mode_t mode) {
switch (mode) {
case AAUDIO_PERFORMANCE_MODE_NONE:
return "NONE";
case AAUDIO_PERFORMANCE_MODE_POWER_SAVING:
return "POWER_SAVING";
case AAUDIO_PERFORMANCE_MODE_LOW_LATENCY:
return "LOW_LATENCY";
default:
return "UNKNOWN";
}
}
const char* FormatToString(int32_t id) {
switch (id) {
case AAUDIO_FORMAT_INVALID:
return "INVALID";
case AAUDIO_FORMAT_UNSPECIFIED:
return "UNSPECIFIED";
case AAUDIO_FORMAT_PCM_I16:
return "PCM_I16";
case AAUDIO_FORMAT_PCM_FLOAT:
return "FLOAT";
default:
return "UNKNOWN";
}
}
void ErrorCallback(AAudioStream* stream,
void* user_data,
aaudio_result_t error) {
RTC_DCHECK(user_data);
AAudioWrapper* aaudio_wrapper = reinterpret_cast<AAudioWrapper*>(user_data);
RTC_LOG(LS_WARNING) << "ErrorCallback: "
<< DirectionToString(aaudio_wrapper->direction());
RTC_DCHECK(aaudio_wrapper->observer());
aaudio_wrapper->observer()->OnErrorCallback(error);
}
aaudio_data_callback_result_t DataCallback(AAudioStream* stream,
void* user_data,
void* audio_data,
int32_t num_frames) {
RTC_DCHECK(user_data);
RTC_DCHECK(audio_data);
AAudioWrapper* aaudio_wrapper = reinterpret_cast<AAudioWrapper*>(user_data);
RTC_DCHECK(aaudio_wrapper->observer());
return aaudio_wrapper->observer()->OnDataCallback(audio_data, num_frames);
}
// Wraps the stream builder object to ensure that it is released properly when
// the stream builder goes out of scope.
class ScopedStreamBuilder {
public:
ScopedStreamBuilder() {
LOG_ON_ERROR(AAudio_createStreamBuilder(&builder_));
RTC_DCHECK(builder_);
}
~ScopedStreamBuilder() {
if (builder_) {
LOG_ON_ERROR(AAudioStreamBuilder_delete(builder_));
}
}
AAudioStreamBuilder* get() const { return builder_; }
private:
AAudioStreamBuilder* builder_ = nullptr;
};
} // namespace
AAudioWrapper::AAudioWrapper(AudioManager* audio_manager,
aaudio_direction_t direction,
AAudioObserverInterface* observer)
: direction_(direction), observer_(observer) {
RTC_LOG(LS_INFO) << "ctor";
RTC_DCHECK(observer_);
direction_ == AAUDIO_DIRECTION_OUTPUT
? audio_parameters_ = audio_manager->GetPlayoutAudioParameters()
: audio_parameters_ = audio_manager->GetRecordAudioParameters();
aaudio_thread_checker_.Detach();
RTC_LOG(LS_INFO) << audio_parameters_.ToString();
}
AAudioWrapper::~AAudioWrapper() {
RTC_LOG(LS_INFO) << "dtor";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!stream_);
}
bool AAudioWrapper::Init() {
RTC_LOG(LS_INFO) << "Init";
RTC_DCHECK(thread_checker_.IsCurrent());
// Creates a stream builder which can be used to open an audio stream.
ScopedStreamBuilder builder;
// Configures the stream builder using audio parameters given at construction.
SetStreamConfiguration(builder.get());
// Opens a stream based on options in the stream builder.
if (!OpenStream(builder.get())) {
return false;
}
// Ensures that the opened stream could activate the requested settings.
if (!VerifyStreamConfiguration()) {
return false;
}
// Optimizes the buffer scheme for lowest possible latency and creates
// additional buffer logic to match the 10ms buffer size used in WebRTC.
if (!OptimizeBuffers()) {
return false;
}
LogStreamState();
return true;
}
bool AAudioWrapper::Start() {
RTC_LOG(LS_INFO) << "Start";
RTC_DCHECK(thread_checker_.IsCurrent());
// TODO(henrika): this state check might not be needed.
aaudio_stream_state_t current_state = AAudioStream_getState(stream_);
if (current_state != AAUDIO_STREAM_STATE_OPEN) {
RTC_LOG(LS_ERROR) << "Invalid state: "
<< AAudio_convertStreamStateToText(current_state);
return false;
}
// Asynchronous request for the stream to start.
RETURN_ON_ERROR(AAudioStream_requestStart(stream_), false);
LogStreamState();
return true;
}
bool AAudioWrapper::Stop() {
RTC_LOG(LS_INFO) << "Stop: " << DirectionToString(direction());
RTC_DCHECK(thread_checker_.IsCurrent());
// Asynchronous request for the stream to stop.
RETURN_ON_ERROR(AAudioStream_requestStop(stream_), false);
CloseStream();
aaudio_thread_checker_.Detach();
return true;
}
double AAudioWrapper::EstimateLatencyMillis() const {
RTC_DCHECK(stream_);
double latency_millis = 0.0;
if (direction() == AAUDIO_DIRECTION_INPUT) {
// For input streams. Best guess we can do is to use the current burst size
// as delay estimate.
latency_millis = static_cast<double>(frames_per_burst()) / sample_rate() *
rtc::kNumMillisecsPerSec;
} else {
int64_t existing_frame_index;
int64_t existing_frame_presentation_time;
// Get the time at which a particular frame was presented to audio hardware.
aaudio_result_t result = AAudioStream_getTimestamp(
stream_, CLOCK_MONOTONIC, &existing_frame_index,
&existing_frame_presentation_time);
// Results are only valid when the stream is in AAUDIO_STREAM_STATE_STARTED.
if (result == AAUDIO_OK) {
// Get write index for next audio frame.
int64_t next_frame_index = frames_written();
// Number of frames between next frame and the existing frame.
int64_t frame_index_delta = next_frame_index - existing_frame_index;
// Assume the next frame will be written now.
int64_t next_frame_write_time = rtc::TimeNanos();
// Calculate time when next frame will be presented to the hardware taking
// sample rate into account.
int64_t frame_time_delta =
(frame_index_delta * rtc::kNumNanosecsPerSec) / sample_rate();
int64_t next_frame_presentation_time =
existing_frame_presentation_time + frame_time_delta;
// Derive a latency estimate given results above.
latency_millis = static_cast<double>(next_frame_presentation_time -
next_frame_write_time) /
rtc::kNumNanosecsPerMillisec;
}
}
return latency_millis;
}
// Returns new buffer size or a negative error value if buffer size could not
// be increased.
bool AAudioWrapper::IncreaseOutputBufferSize() {
RTC_LOG(LS_INFO) << "IncreaseBufferSize";
RTC_DCHECK(stream_);
RTC_DCHECK(aaudio_thread_checker_.IsCurrent());
RTC_DCHECK_EQ(direction(), AAUDIO_DIRECTION_OUTPUT);
aaudio_result_t buffer_size = AAudioStream_getBufferSizeInFrames(stream_);
// Try to increase size of buffer with one burst to reduce risk of underrun.
buffer_size += frames_per_burst();
// Verify that the new buffer size is not larger than max capacity.
// TODO(henrika): keep track of case when we reach the capacity limit.
const int32_t max_buffer_size = buffer_capacity_in_frames();
if (buffer_size > max_buffer_size) {
RTC_LOG(LS_ERROR) << "Required buffer size (" << buffer_size
<< ") is higher than max: " << max_buffer_size;
return false;
}
RTC_LOG(LS_INFO) << "Updating buffer size to: " << buffer_size
<< " (max=" << max_buffer_size << ")";
buffer_size = AAudioStream_setBufferSizeInFrames(stream_, buffer_size);
if (buffer_size < 0) {
RTC_LOG(LS_ERROR) << "Failed to change buffer size: "
<< AAudio_convertResultToText(buffer_size);
return false;
}
RTC_LOG(LS_INFO) << "Buffer size changed to: " << buffer_size;
return true;
}
void AAudioWrapper::ClearInputStream(void* audio_data, int32_t num_frames) {
RTC_LOG(LS_INFO) << "ClearInputStream";
RTC_DCHECK(stream_);
RTC_DCHECK(aaudio_thread_checker_.IsCurrent());
RTC_DCHECK_EQ(direction(), AAUDIO_DIRECTION_INPUT);
aaudio_result_t cleared_frames = 0;
do {
cleared_frames = AAudioStream_read(stream_, audio_data, num_frames, 0);
} while (cleared_frames > 0);
}
AAudioObserverInterface* AAudioWrapper::observer() const {
return observer_;
}
AudioParameters AAudioWrapper::audio_parameters() const {
return audio_parameters_;
}
int32_t AAudioWrapper::samples_per_frame() const {
RTC_DCHECK(stream_);
return AAudioStream_getSamplesPerFrame(stream_);
}
int32_t AAudioWrapper::buffer_size_in_frames() const {
RTC_DCHECK(stream_);
return AAudioStream_getBufferSizeInFrames(stream_);
}
int32_t AAudioWrapper::buffer_capacity_in_frames() const {
RTC_DCHECK(stream_);
return AAudioStream_getBufferCapacityInFrames(stream_);
}
int32_t AAudioWrapper::device_id() const {
RTC_DCHECK(stream_);
return AAudioStream_getDeviceId(stream_);
}
int32_t AAudioWrapper::xrun_count() const {
RTC_DCHECK(stream_);
return AAudioStream_getXRunCount(stream_);
}
int32_t AAudioWrapper::format() const {
RTC_DCHECK(stream_);
return AAudioStream_getFormat(stream_);
}
int32_t AAudioWrapper::sample_rate() const {
RTC_DCHECK(stream_);
return AAudioStream_getSampleRate(stream_);
}
int32_t AAudioWrapper::channel_count() const {
RTC_DCHECK(stream_);
return AAudioStream_getChannelCount(stream_);
}
int32_t AAudioWrapper::frames_per_callback() const {
RTC_DCHECK(stream_);
return AAudioStream_getFramesPerDataCallback(stream_);
}
aaudio_sharing_mode_t AAudioWrapper::sharing_mode() const {
RTC_DCHECK(stream_);
return AAudioStream_getSharingMode(stream_);
}
aaudio_performance_mode_t AAudioWrapper::performance_mode() const {
RTC_DCHECK(stream_);
return AAudioStream_getPerformanceMode(stream_);
}
aaudio_stream_state_t AAudioWrapper::stream_state() const {
RTC_DCHECK(stream_);
return AAudioStream_getState(stream_);
}
int64_t AAudioWrapper::frames_written() const {
RTC_DCHECK(stream_);
return AAudioStream_getFramesWritten(stream_);
}
int64_t AAudioWrapper::frames_read() const {
RTC_DCHECK(stream_);
return AAudioStream_getFramesRead(stream_);
}
void AAudioWrapper::SetStreamConfiguration(AAudioStreamBuilder* builder) {
RTC_LOG(LS_INFO) << "SetStreamConfiguration";
RTC_DCHECK(builder);
RTC_DCHECK(thread_checker_.IsCurrent());
// Request usage of default primary output/input device.
// TODO(henrika): verify that default device follows Java APIs.
// https://developer.android.com/reference/android/media/AudioDeviceInfo.html.
AAudioStreamBuilder_setDeviceId(builder, AAUDIO_UNSPECIFIED);
// Use preferred sample rate given by the audio parameters.
AAudioStreamBuilder_setSampleRate(builder, audio_parameters().sample_rate());
// Use preferred channel configuration given by the audio parameters.
AAudioStreamBuilder_setChannelCount(builder, audio_parameters().channels());
// Always use 16-bit PCM audio sample format.
AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
// TODO(henrika): investigate effect of using AAUDIO_SHARING_MODE_EXCLUSIVE.
// Ask for exclusive mode since this will give us the lowest possible latency.
// If exclusive mode isn't available, shared mode will be used instead.
AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_SHARED);
// Use the direction that was given at construction.
AAudioStreamBuilder_setDirection(builder, direction_);
// TODO(henrika): investigate performance using different performance modes.
AAudioStreamBuilder_setPerformanceMode(builder,
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
// Given that WebRTC applications require low latency, our audio stream uses
// an asynchronous callback function to transfer data to and from the
// application. AAudio executes the callback in a higher-priority thread that
// has better performance.
AAudioStreamBuilder_setDataCallback(builder, DataCallback, this);
// Request that AAudio calls this functions if any error occurs on a callback
// thread.
AAudioStreamBuilder_setErrorCallback(builder, ErrorCallback, this);
}
bool AAudioWrapper::OpenStream(AAudioStreamBuilder* builder) {
RTC_LOG(LS_INFO) << "OpenStream";
RTC_DCHECK(builder);
AAudioStream* stream = nullptr;
RETURN_ON_ERROR(AAudioStreamBuilder_openStream(builder, &stream), false);
stream_ = stream;
LogStreamConfiguration();
return true;
}
void AAudioWrapper::CloseStream() {
RTC_LOG(LS_INFO) << "CloseStream";
RTC_DCHECK(stream_);
LOG_ON_ERROR(AAudioStream_close(stream_));
stream_ = nullptr;
}
void AAudioWrapper::LogStreamConfiguration() {
RTC_DCHECK(stream_);
char ss_buf[1024];
rtc::SimpleStringBuilder ss(ss_buf);
ss << "Stream Configuration: ";
ss << "sample rate=" << sample_rate() << ", channels=" << channel_count();
ss << ", samples per frame=" << samples_per_frame();
ss << ", format=" << FormatToString(format());
ss << ", sharing mode=" << SharingModeToString(sharing_mode());
ss << ", performance mode=" << PerformanceModeToString(performance_mode());
ss << ", direction=" << DirectionToString(direction());
ss << ", device id=" << AAudioStream_getDeviceId(stream_);
ss << ", frames per callback=" << frames_per_callback();
RTC_LOG(LS_INFO) << ss.str();
}
void AAudioWrapper::LogStreamState() {
RTC_LOG(LS_INFO) << "AAudio stream state: "
<< AAudio_convertStreamStateToText(stream_state());
}
bool AAudioWrapper::VerifyStreamConfiguration() {
RTC_LOG(LS_INFO) << "VerifyStreamConfiguration";
RTC_DCHECK(stream_);
// TODO(henrika): should we verify device ID as well?
if (AAudioStream_getSampleRate(stream_) != audio_parameters().sample_rate()) {
RTC_LOG(LS_ERROR) << "Stream unable to use requested sample rate";
return false;
}
if (AAudioStream_getChannelCount(stream_) !=
static_cast<int32_t>(audio_parameters().channels())) {
RTC_LOG(LS_ERROR) << "Stream unable to use requested channel count";
return false;
}
if (AAudioStream_getFormat(stream_) != AAUDIO_FORMAT_PCM_I16) {
RTC_LOG(LS_ERROR) << "Stream unable to use requested format";
return false;
}
if (AAudioStream_getSharingMode(stream_) != AAUDIO_SHARING_MODE_SHARED) {
RTC_LOG(LS_ERROR) << "Stream unable to use requested sharing mode";
return false;
}
if (AAudioStream_getPerformanceMode(stream_) !=
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY) {
RTC_LOG(LS_ERROR) << "Stream unable to use requested performance mode";
return false;
}
if (AAudioStream_getDirection(stream_) != direction()) {
RTC_LOG(LS_ERROR) << "Stream direction could not be set";
return false;
}
if (AAudioStream_getSamplesPerFrame(stream_) !=
static_cast<int32_t>(audio_parameters().channels())) {
RTC_LOG(LS_ERROR) << "Invalid number of samples per frame";
return false;
}
return true;
}
bool AAudioWrapper::OptimizeBuffers() {
RTC_LOG(LS_INFO) << "OptimizeBuffers";
RTC_DCHECK(stream_);
// Maximum number of frames that can be filled without blocking.
RTC_LOG(LS_INFO) << "max buffer capacity in frames: "
<< buffer_capacity_in_frames();
// Query the number of frames that the application should read or write at
// one time for optimal performance.
int32_t frames_per_burst = AAudioStream_getFramesPerBurst(stream_);
RTC_LOG(LS_INFO) << "frames per burst for optimal performance: "
<< frames_per_burst;
frames_per_burst_ = frames_per_burst;
if (direction() == AAUDIO_DIRECTION_INPUT) {
// There is no point in calling setBufferSizeInFrames() for input streams
// since it has no effect on the performance (latency in this case).
return true;
}
// Set buffer size to same as burst size to guarantee lowest possible latency.
// This size might change for output streams if underruns are detected and
// automatic buffer adjustment is enabled.
AAudioStream_setBufferSizeInFrames(stream_, frames_per_burst);
int32_t buffer_size = AAudioStream_getBufferSizeInFrames(stream_);
if (buffer_size != frames_per_burst) {
RTC_LOG(LS_ERROR) << "Failed to use optimal buffer burst size";
return false;
}
// Maximum number of frames that can be filled without blocking.
RTC_LOG(LS_INFO) << "buffer burst size in frames: " << buffer_size;
return true;
}
} // namespace webrtc

View file

@ -0,0 +1,127 @@
/*
* Copyright (c) 2018 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_AAUDIO_WRAPPER_H_
#define MODULES_AUDIO_DEVICE_ANDROID_AAUDIO_WRAPPER_H_
#include <aaudio/AAudio.h>
#include "api/sequence_checker.h"
#include "modules/audio_device/include/audio_device_defines.h"
namespace webrtc {
class AudioManager;
// AAudio callback interface for audio transport to/from the AAudio stream.
// The interface also contains an error callback method for notifications of
// e.g. device changes.
class AAudioObserverInterface {
public:
// Audio data will be passed in our out of this function dependning on the
// direction of the audio stream. This callback function will be called on a
// real-time thread owned by AAudio.
virtual aaudio_data_callback_result_t OnDataCallback(void* audio_data,
int32_t num_frames) = 0;
// AAudio will call this functions if any error occurs on a callback thread.
// In response, this function could signal or launch another thread to reopen
// a stream on another device. Do not reopen the stream in this callback.
virtual void OnErrorCallback(aaudio_result_t error) = 0;
protected:
virtual ~AAudioObserverInterface() {}
};
// Utility class which wraps the C-based AAudio API into a more handy C++ class
// where the underlying resources (AAudioStreamBuilder and AAudioStream) are
// encapsulated. User must set the direction (in or out) at construction since
// it defines the stream type and the direction of the data flow in the
// AAudioObserverInterface.
//
// AAudio is a new Android C API introduced in the Android O (26) release.
// It is designed for high-performance audio applications that require low
// latency. Applications communicate with AAudio by reading and writing data
// to streams.
//
// Each stream is attached to a single audio device, where each audio device
// has a unique ID. The ID can be used to bind an audio stream to a specific
// audio device but this implementation lets AAudio choose the default primary
// device instead (device selection takes place in Java). A stream can only
// move data in one direction. When a stream is opened, Android checks to
// ensure that the audio device and stream direction agree.
class AAudioWrapper {
public:
AAudioWrapper(AudioManager* audio_manager,
aaudio_direction_t direction,
AAudioObserverInterface* observer);
~AAudioWrapper();
bool Init();
bool Start();
bool Stop();
// For output streams: estimates latency between writing an audio frame to
// the output stream and the time that same frame is played out on the output
// audio device.
// For input streams: estimates latency between reading an audio frame from
// the input stream and the time that same frame was recorded on the input
// audio device.
double EstimateLatencyMillis() const;
// Increases the internal buffer size for output streams by one burst size to
// reduce the risk of underruns. Can be used while a stream is active.
bool IncreaseOutputBufferSize();
// Drains the recording stream of any existing data by reading from it until
// it's empty. Can be used to clear out old data before starting a new audio
// session.
void ClearInputStream(void* audio_data, int32_t num_frames);
AAudioObserverInterface* observer() const;
AudioParameters audio_parameters() const;
int32_t samples_per_frame() const;
int32_t buffer_size_in_frames() const;
int32_t buffer_capacity_in_frames() const;
int32_t device_id() const;
int32_t xrun_count() const;
int32_t format() const;
int32_t sample_rate() const;
int32_t channel_count() const;
int32_t frames_per_callback() const;
aaudio_sharing_mode_t sharing_mode() const;
aaudio_performance_mode_t performance_mode() const;
aaudio_stream_state_t stream_state() const;
int64_t frames_written() const;
int64_t frames_read() const;
aaudio_direction_t direction() const { return direction_; }
AAudioStream* stream() const { return stream_; }
int32_t frames_per_burst() const { return frames_per_burst_; }
private:
void SetStreamConfiguration(AAudioStreamBuilder* builder);
bool OpenStream(AAudioStreamBuilder* builder);
void CloseStream();
void LogStreamConfiguration();
void LogStreamState();
bool VerifyStreamConfiguration();
bool OptimizeBuffers();
SequenceChecker thread_checker_;
SequenceChecker aaudio_thread_checker_;
AudioParameters audio_parameters_;
const aaudio_direction_t direction_;
AAudioObserverInterface* observer_ = nullptr;
AAudioStream* stream_ = nullptr;
int32_t frames_per_burst_ = 0;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_AAUDIO_WRAPPER_H_

View file

@ -0,0 +1,28 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_AUDIO_COMMON_H_
#define MODULES_AUDIO_DEVICE_ANDROID_AUDIO_COMMON_H_
namespace webrtc {
const int kDefaultSampleRate = 44100;
// Delay estimates for the two different supported modes. These values are based
// on real-time round-trip delay estimates on a large set of devices and they
// are lower bounds since the filter length is 128 ms, so the AEC works for
// delays in the range [50, ~170] ms and [150, ~270] ms. Note that, in most
// cases, the lowest delay estimate will not be utilized since devices that
// support low-latency output audio often supports HW AEC as well.
const int kLowLatencyModeDelayEstimateInMilliseconds = 50;
const int kHighLatencyModeDelayEstimateInMilliseconds = 150;
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_AUDIO_COMMON_H_

View file

@ -0,0 +1,441 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_AUDIO_DEVICE_TEMPLATE_H_
#define MODULES_AUDIO_DEVICE_ANDROID_AUDIO_DEVICE_TEMPLATE_H_
#include "api/sequence_checker.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/audio_device_generic.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
// InputType/OutputType can be any class that implements the capturing/rendering
// part of the AudioDeviceGeneric API.
// Construction and destruction must be done on one and the same thread. Each
// internal implementation of InputType and OutputType will RTC_DCHECK if that
// is not the case. All implemented methods must also be called on the same
// thread. See comments in each InputType/OutputType class for more info.
// It is possible to call the two static methods (SetAndroidAudioDeviceObjects
// and ClearAndroidAudioDeviceObjects) from a different thread but both will
// RTC_CHECK that the calling thread is attached to a Java VM.
template <class InputType, class OutputType>
class AudioDeviceTemplate : public AudioDeviceGeneric {
public:
AudioDeviceTemplate(AudioDeviceModule::AudioLayer audio_layer,
AudioManager* audio_manager)
: audio_layer_(audio_layer),
audio_manager_(audio_manager),
output_(audio_manager_),
input_(audio_manager_),
initialized_(false) {
RTC_DLOG(LS_INFO) << __FUNCTION__;
RTC_CHECK(audio_manager);
audio_manager_->SetActiveAudioLayer(audio_layer);
}
virtual ~AudioDeviceTemplate() { RTC_LOG(LS_INFO) << __FUNCTION__; }
int32_t ActiveAudioLayer(
AudioDeviceModule::AudioLayer& audioLayer) const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
audioLayer = audio_layer_;
return 0;
}
InitStatus Init() override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!initialized_);
if (!audio_manager_->Init()) {
return InitStatus::OTHER_ERROR;
}
if (output_.Init() != 0) {
audio_manager_->Close();
return InitStatus::PLAYOUT_ERROR;
}
if (input_.Init() != 0) {
output_.Terminate();
audio_manager_->Close();
return InitStatus::RECORDING_ERROR;
}
initialized_ = true;
return InitStatus::OK;
}
int32_t Terminate() override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
RTC_DCHECK(thread_checker_.IsCurrent());
int32_t err = input_.Terminate();
err |= output_.Terminate();
err |= !audio_manager_->Close();
initialized_ = false;
RTC_DCHECK_EQ(err, 0);
return err;
}
bool Initialized() const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
RTC_DCHECK(thread_checker_.IsCurrent());
return initialized_;
}
int16_t PlayoutDevices() override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return 1;
}
int16_t RecordingDevices() override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return 1;
}
int32_t PlayoutDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) override {
return 0;
}
int32_t RecordingDeviceName(uint16_t index,
char name[kAdmMaxDeviceNameSize],
char guid[kAdmMaxGuidSize]) override {
return 0;
}
int32_t SetPlayoutDevice(uint16_t index) override {
// OK to use but it has no effect currently since device selection is
// done using Andoid APIs instead.
RTC_DLOG(LS_INFO) << __FUNCTION__;
return 0;
}
int32_t SetPlayoutDevice(
AudioDeviceModule::WindowsDeviceType device) override {
// OK to use but it has no effect currently since device selection is
// done using Andoid APIs instead.
RTC_DLOG(LS_INFO) << __FUNCTION__;
return 0;
}
int32_t SetRecordingDevice(uint16_t index) override {
// OK to use but it has no effect currently since device selection is
// done using Andoid APIs instead.
RTC_DLOG(LS_INFO) << __FUNCTION__;
return 0;
}
int32_t SetRecordingDevice(
AudioDeviceModule::WindowsDeviceType device) override {
// OK to use but it has no effect currently since device selection is
// done using Andoid APIs instead.
RTC_DLOG(LS_INFO) << __FUNCTION__;
return 0;
}
int32_t PlayoutIsAvailable(bool& available) override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
available = true;
return 0;
}
int32_t InitPlayout() override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return output_.InitPlayout();
}
bool PlayoutIsInitialized() const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return output_.PlayoutIsInitialized();
}
int32_t RecordingIsAvailable(bool& available) override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
available = true;
return 0;
}
int32_t InitRecording() override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return input_.InitRecording();
}
bool RecordingIsInitialized() const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return input_.RecordingIsInitialized();
}
int32_t StartPlayout() override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
if (!audio_manager_->IsCommunicationModeEnabled()) {
RTC_LOG(LS_WARNING)
<< "The application should use MODE_IN_COMMUNICATION audio mode!";
}
return output_.StartPlayout();
}
int32_t StopPlayout() override {
// Avoid using audio manger (JNI/Java cost) if playout was inactive.
if (!Playing())
return 0;
RTC_DLOG(LS_INFO) << __FUNCTION__;
int32_t err = output_.StopPlayout();
return err;
}
bool Playing() const override {
RTC_LOG(LS_INFO) << __FUNCTION__;
return output_.Playing();
}
int32_t StartRecording() override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
if (!audio_manager_->IsCommunicationModeEnabled()) {
RTC_LOG(LS_WARNING)
<< "The application should use MODE_IN_COMMUNICATION audio mode!";
}
return input_.StartRecording();
}
int32_t StopRecording() override {
// Avoid using audio manger (JNI/Java cost) if recording was inactive.
RTC_DLOG(LS_INFO) << __FUNCTION__;
if (!Recording())
return 0;
int32_t err = input_.StopRecording();
return err;
}
bool Recording() const override { return input_.Recording(); }
int32_t InitSpeaker() override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return 0;
}
bool SpeakerIsInitialized() const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return true;
}
int32_t InitMicrophone() override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return 0;
}
bool MicrophoneIsInitialized() const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return true;
}
int32_t SpeakerVolumeIsAvailable(bool& available) override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return output_.SpeakerVolumeIsAvailable(available);
}
int32_t SetSpeakerVolume(uint32_t volume) override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return output_.SetSpeakerVolume(volume);
}
int32_t SpeakerVolume(uint32_t& volume) const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return output_.SpeakerVolume(volume);
}
int32_t MaxSpeakerVolume(uint32_t& maxVolume) const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return output_.MaxSpeakerVolume(maxVolume);
}
int32_t MinSpeakerVolume(uint32_t& minVolume) const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return output_.MinSpeakerVolume(minVolume);
}
int32_t MicrophoneVolumeIsAvailable(bool& available) override {
available = false;
return -1;
}
int32_t SetMicrophoneVolume(uint32_t volume) override {
RTC_CHECK_NOTREACHED();
}
int32_t MicrophoneVolume(uint32_t& volume) const override {
RTC_CHECK_NOTREACHED();
return -1;
}
int32_t MaxMicrophoneVolume(uint32_t& maxVolume) const override {
RTC_CHECK_NOTREACHED();
}
int32_t MinMicrophoneVolume(uint32_t& minVolume) const override {
RTC_CHECK_NOTREACHED();
}
int32_t SpeakerMuteIsAvailable(bool& available) override {
return false;
}
int32_t SetSpeakerMute(bool enable) override { RTC_CHECK_NOTREACHED(); }
int32_t SpeakerMute(bool& enabled) const override { RTC_CHECK_NOTREACHED(); }
int32_t MicrophoneMuteIsAvailable(bool& available) override {
return false;
}
int32_t SetMicrophoneMute(bool enable) override { RTC_CHECK_NOTREACHED(); }
int32_t MicrophoneMute(bool& enabled) const override {
RTC_CHECK_NOTREACHED();
}
// Returns true if the audio manager has been configured to support stereo
// and false otherwised. Default is mono.
int32_t StereoPlayoutIsAvailable(bool& available) override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
available = audio_manager_->IsStereoPlayoutSupported();
return 0;
}
int32_t SetStereoPlayout(bool enable) override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
bool available = audio_manager_->IsStereoPlayoutSupported();
// Android does not support changes between mono and stero on the fly.
// Instead, the native audio layer is configured via the audio manager
// to either support mono or stereo. It is allowed to call this method
// if that same state is not modified.
return (enable == available) ? 0 : -1;
}
int32_t StereoPlayout(bool& enabled) const override {
enabled = audio_manager_->IsStereoPlayoutSupported();
return 0;
}
int32_t StereoRecordingIsAvailable(bool& available) override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
available = audio_manager_->IsStereoRecordSupported();
return 0;
}
int32_t SetStereoRecording(bool enable) override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
bool available = audio_manager_->IsStereoRecordSupported();
// Android does not support changes between mono and stero on the fly.
// Instead, the native audio layer is configured via the audio manager
// to either support mono or stereo. It is allowed to call this method
// if that same state is not modified.
return (enable == available) ? 0 : -1;
}
int32_t StereoRecording(bool& enabled) const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
enabled = audio_manager_->IsStereoRecordSupported();
return 0;
}
int32_t PlayoutDelay(uint16_t& delay_ms) const override {
// Best guess we can do is to use half of the estimated total delay.
delay_ms = audio_manager_->GetDelayEstimateInMilliseconds() / 2;
RTC_DCHECK_GT(delay_ms, 0);
return 0;
}
void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
output_.AttachAudioBuffer(audioBuffer);
input_.AttachAudioBuffer(audioBuffer);
}
// Returns true if the device both supports built in AEC and the device
// is not blacklisted.
// Currently, if OpenSL ES is used in both directions, this method will still
// report the correct value and it has the correct effect. As an example:
// a device supports built in AEC and this method returns true. Libjingle
// will then disable the WebRTC based AEC and that will work for all devices
// (mainly Nexus) even when OpenSL ES is used for input since our current
// implementation will enable built-in AEC by default also for OpenSL ES.
// The only "bad" thing that happens today is that when Libjingle calls
// OpenSLESRecorder::EnableBuiltInAEC() it will not have any real effect and
// a "Not Implemented" log will be filed. This non-perfect state will remain
// until I have added full support for audio effects based on OpenSL ES APIs.
bool BuiltInAECIsAvailable() const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return audio_manager_->IsAcousticEchoCancelerSupported();
}
// TODO(henrika): add implementation for OpenSL ES based audio as well.
int32_t EnableBuiltInAEC(bool enable) override {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << enable << ")";
RTC_CHECK(BuiltInAECIsAvailable()) << "HW AEC is not available";
return input_.EnableBuiltInAEC(enable);
}
// Returns true if the device both supports built in AGC and the device
// is not blacklisted.
// TODO(henrika): add implementation for OpenSL ES based audio as well.
// In addition, see comments for BuiltInAECIsAvailable().
bool BuiltInAGCIsAvailable() const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return audio_manager_->IsAutomaticGainControlSupported();
}
// TODO(henrika): add implementation for OpenSL ES based audio as well.
int32_t EnableBuiltInAGC(bool enable) override {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << enable << ")";
RTC_CHECK(BuiltInAGCIsAvailable()) << "HW AGC is not available";
return input_.EnableBuiltInAGC(enable);
}
// Returns true if the device both supports built in NS and the device
// is not blacklisted.
// TODO(henrika): add implementation for OpenSL ES based audio as well.
// In addition, see comments for BuiltInAECIsAvailable().
bool BuiltInNSIsAvailable() const override {
RTC_DLOG(LS_INFO) << __FUNCTION__;
return audio_manager_->IsNoiseSuppressorSupported();
}
// TODO(henrika): add implementation for OpenSL ES based audio as well.
int32_t EnableBuiltInNS(bool enable) override {
RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << enable << ")";
RTC_CHECK(BuiltInNSIsAvailable()) << "HW NS is not available";
return input_.EnableBuiltInNS(enable);
}
private:
SequenceChecker thread_checker_;
// Local copy of the audio layer set during construction of the
// AudioDeviceModuleImpl instance. Read only value.
const AudioDeviceModule::AudioLayer audio_layer_;
// Non-owning raw pointer to AudioManager instance given to use at
// construction. The real object is owned by AudioDeviceModuleImpl and the
// life time is the same as that of the AudioDeviceModuleImpl, hence there
// is no risk of reading a NULL pointer at any time in this class.
AudioManager* const audio_manager_;
OutputType output_;
InputType input_;
bool initialized_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_AUDIO_DEVICE_TEMPLATE_H_

View file

@ -0,0 +1,318 @@
/*
* Copyright (c) 2015 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 "modules/audio_device/android/audio_manager.h"
#include <utility>
#include "modules/audio_device/android/audio_common.h"
#include "modules/utility/include/helpers_android.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/platform_thread.h"
namespace webrtc {
// AudioManager::JavaAudioManager implementation
AudioManager::JavaAudioManager::JavaAudioManager(
NativeRegistration* native_reg,
std::unique_ptr<GlobalRef> audio_manager)
: audio_manager_(std::move(audio_manager)),
init_(native_reg->GetMethodId("init", "()Z")),
dispose_(native_reg->GetMethodId("dispose", "()V")),
is_communication_mode_enabled_(
native_reg->GetMethodId("isCommunicationModeEnabled", "()Z")),
is_device_blacklisted_for_open_sles_usage_(
native_reg->GetMethodId("isDeviceBlacklistedForOpenSLESUsage",
"()Z")) {
RTC_LOG(LS_INFO) << "JavaAudioManager::ctor";
}
AudioManager::JavaAudioManager::~JavaAudioManager() {
RTC_LOG(LS_INFO) << "JavaAudioManager::~dtor";
}
bool AudioManager::JavaAudioManager::Init() {
return audio_manager_->CallBooleanMethod(init_);
}
void AudioManager::JavaAudioManager::Close() {
audio_manager_->CallVoidMethod(dispose_);
}
bool AudioManager::JavaAudioManager::IsCommunicationModeEnabled() {
return audio_manager_->CallBooleanMethod(is_communication_mode_enabled_);
}
bool AudioManager::JavaAudioManager::IsDeviceBlacklistedForOpenSLESUsage() {
return audio_manager_->CallBooleanMethod(
is_device_blacklisted_for_open_sles_usage_);
}
// AudioManager implementation
AudioManager::AudioManager()
: j_environment_(JVM::GetInstance()->environment()),
audio_layer_(AudioDeviceModule::kPlatformDefaultAudio),
initialized_(false),
hardware_aec_(false),
hardware_agc_(false),
hardware_ns_(false),
low_latency_playout_(false),
low_latency_record_(false),
delay_estimate_in_milliseconds_(0) {
RTC_LOG(LS_INFO) << "ctor";
RTC_CHECK(j_environment_);
JNINativeMethod native_methods[] = {
{"nativeCacheAudioParameters", "(IIIZZZZZZZIIJ)V",
reinterpret_cast<void*>(&webrtc::AudioManager::CacheAudioParameters)}};
j_native_registration_ = j_environment_->RegisterNatives(
"org/webrtc/voiceengine/WebRtcAudioManager", native_methods,
arraysize(native_methods));
j_audio_manager_.reset(
new JavaAudioManager(j_native_registration_.get(),
j_native_registration_->NewObject(
"<init>", "(J)V", PointerTojlong(this))));
}
AudioManager::~AudioManager() {
RTC_LOG(LS_INFO) << "dtor";
RTC_DCHECK(thread_checker_.IsCurrent());
Close();
}
void AudioManager::SetActiveAudioLayer(
AudioDeviceModule::AudioLayer audio_layer) {
RTC_LOG(LS_INFO) << "SetActiveAudioLayer: " << audio_layer;
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!initialized_);
// Store the currently utilized audio layer.
audio_layer_ = audio_layer;
// The delay estimate can take one of two fixed values depending on if the
// device supports low-latency output or not. However, it is also possible
// that the user explicitly selects the high-latency audio path, hence we use
// the selected `audio_layer` here to set the delay estimate.
delay_estimate_in_milliseconds_ =
(audio_layer == AudioDeviceModule::kAndroidJavaAudio)
? kHighLatencyModeDelayEstimateInMilliseconds
: kLowLatencyModeDelayEstimateInMilliseconds;
RTC_LOG(LS_INFO) << "delay_estimate_in_milliseconds: "
<< delay_estimate_in_milliseconds_;
}
SLObjectItf AudioManager::GetOpenSLEngine() {
RTC_LOG(LS_INFO) << "GetOpenSLEngine";
RTC_DCHECK(thread_checker_.IsCurrent());
// Only allow usage of OpenSL ES if such an audio layer has been specified.
if (audio_layer_ != AudioDeviceModule::kAndroidOpenSLESAudio &&
audio_layer_ !=
AudioDeviceModule::kAndroidJavaInputAndOpenSLESOutputAudio) {
RTC_LOG(LS_INFO)
<< "Unable to create OpenSL engine for the current audio layer: "
<< audio_layer_;
return nullptr;
}
// OpenSL ES for Android only supports a single engine per application.
// If one already has been created, return existing object instead of
// creating a new.
if (engine_object_.Get() != nullptr) {
RTC_LOG(LS_WARNING)
<< "The OpenSL ES engine object has already been created";
return engine_object_.Get();
}
// Create the engine object in thread safe mode.
const SLEngineOption option[] = {
{SL_ENGINEOPTION_THREADSAFE, static_cast<SLuint32>(SL_BOOLEAN_TRUE)}};
SLresult result =
slCreateEngine(engine_object_.Receive(), 1, option, 0, NULL, NULL);
if (result != SL_RESULT_SUCCESS) {
RTC_LOG(LS_ERROR) << "slCreateEngine() failed: "
<< GetSLErrorString(result);
engine_object_.Reset();
return nullptr;
}
// Realize the SL Engine in synchronous mode.
result = engine_object_->Realize(engine_object_.Get(), SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
RTC_LOG(LS_ERROR) << "Realize() failed: " << GetSLErrorString(result);
engine_object_.Reset();
return nullptr;
}
// Finally return the SLObjectItf interface of the engine object.
return engine_object_.Get();
}
bool AudioManager::Init() {
RTC_LOG(LS_INFO) << "Init";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!initialized_);
RTC_DCHECK_NE(audio_layer_, AudioDeviceModule::kPlatformDefaultAudio);
if (!j_audio_manager_->Init()) {
RTC_LOG(LS_ERROR) << "Init() failed";
return false;
}
initialized_ = true;
return true;
}
bool AudioManager::Close() {
RTC_LOG(LS_INFO) << "Close";
RTC_DCHECK(thread_checker_.IsCurrent());
if (!initialized_)
return true;
j_audio_manager_->Close();
initialized_ = false;
return true;
}
bool AudioManager::IsCommunicationModeEnabled() const {
RTC_DCHECK(thread_checker_.IsCurrent());
return j_audio_manager_->IsCommunicationModeEnabled();
}
bool AudioManager::IsAcousticEchoCancelerSupported() const {
RTC_DCHECK(thread_checker_.IsCurrent());
return hardware_aec_;
}
bool AudioManager::IsAutomaticGainControlSupported() const {
RTC_DCHECK(thread_checker_.IsCurrent());
return hardware_agc_;
}
bool AudioManager::IsNoiseSuppressorSupported() const {
RTC_DCHECK(thread_checker_.IsCurrent());
return hardware_ns_;
}
bool AudioManager::IsLowLatencyPlayoutSupported() const {
RTC_DCHECK(thread_checker_.IsCurrent());
// Some devices are blacklisted for usage of OpenSL ES even if they report
// that low-latency playout is supported. See b/21485703 for details.
return j_audio_manager_->IsDeviceBlacklistedForOpenSLESUsage()
? false
: low_latency_playout_;
}
bool AudioManager::IsLowLatencyRecordSupported() const {
RTC_DCHECK(thread_checker_.IsCurrent());
return low_latency_record_;
}
bool AudioManager::IsProAudioSupported() const {
RTC_DCHECK(thread_checker_.IsCurrent());
// TODO(henrika): return the state independently of if OpenSL ES is
// blacklisted or not for now. We could use the same approach as in
// IsLowLatencyPlayoutSupported() but I can't see the need for it yet.
return pro_audio_;
}
// TODO(henrika): improve comments...
bool AudioManager::IsAAudioSupported() const {
#if defined(WEBRTC_AUDIO_DEVICE_INCLUDE_ANDROID_AAUDIO)
return a_audio_;
#else
return false;
#endif
}
bool AudioManager::IsStereoPlayoutSupported() const {
RTC_DCHECK(thread_checker_.IsCurrent());
return (playout_parameters_.channels() == 2);
}
bool AudioManager::IsStereoRecordSupported() const {
RTC_DCHECK(thread_checker_.IsCurrent());
return (record_parameters_.channels() == 2);
}
int AudioManager::GetDelayEstimateInMilliseconds() const {
return delay_estimate_in_milliseconds_;
}
JNI_FUNCTION_ALIGN
void JNICALL AudioManager::CacheAudioParameters(JNIEnv* env,
jobject obj,
jint sample_rate,
jint output_channels,
jint input_channels,
jboolean hardware_aec,
jboolean hardware_agc,
jboolean hardware_ns,
jboolean low_latency_output,
jboolean low_latency_input,
jboolean pro_audio,
jboolean a_audio,
jint output_buffer_size,
jint input_buffer_size,
jlong native_audio_manager) {
webrtc::AudioManager* this_object =
reinterpret_cast<webrtc::AudioManager*>(native_audio_manager);
this_object->OnCacheAudioParameters(
env, sample_rate, output_channels, input_channels, hardware_aec,
hardware_agc, hardware_ns, low_latency_output, low_latency_input,
pro_audio, a_audio, output_buffer_size, input_buffer_size);
}
void AudioManager::OnCacheAudioParameters(JNIEnv* env,
jint sample_rate,
jint output_channels,
jint input_channels,
jboolean hardware_aec,
jboolean hardware_agc,
jboolean hardware_ns,
jboolean low_latency_output,
jboolean low_latency_input,
jboolean pro_audio,
jboolean a_audio,
jint output_buffer_size,
jint input_buffer_size) {
RTC_LOG(LS_INFO)
<< "OnCacheAudioParameters: "
"hardware_aec: "
<< static_cast<bool>(hardware_aec)
<< ", hardware_agc: " << static_cast<bool>(hardware_agc)
<< ", hardware_ns: " << static_cast<bool>(hardware_ns)
<< ", low_latency_output: " << static_cast<bool>(low_latency_output)
<< ", low_latency_input: " << static_cast<bool>(low_latency_input)
<< ", pro_audio: " << static_cast<bool>(pro_audio)
<< ", a_audio: " << static_cast<bool>(a_audio)
<< ", sample_rate: " << static_cast<int>(sample_rate)
<< ", output_channels: " << static_cast<int>(output_channels)
<< ", input_channels: " << static_cast<int>(input_channels)
<< ", output_buffer_size: " << static_cast<int>(output_buffer_size)
<< ", input_buffer_size: " << static_cast<int>(input_buffer_size);
RTC_DCHECK(thread_checker_.IsCurrent());
hardware_aec_ = hardware_aec;
hardware_agc_ = hardware_agc;
hardware_ns_ = hardware_ns;
low_latency_playout_ = low_latency_output;
low_latency_record_ = low_latency_input;
pro_audio_ = pro_audio;
a_audio_ = a_audio;
playout_parameters_.reset(sample_rate, static_cast<size_t>(output_channels),
static_cast<size_t>(output_buffer_size));
record_parameters_.reset(sample_rate, static_cast<size_t>(input_channels),
static_cast<size_t>(input_buffer_size));
}
const AudioParameters& AudioManager::GetPlayoutAudioParameters() {
RTC_CHECK(playout_parameters_.is_valid());
RTC_DCHECK(thread_checker_.IsCurrent());
return playout_parameters_;
}
const AudioParameters& AudioManager::GetRecordAudioParameters() {
RTC_CHECK(record_parameters_.is_valid());
RTC_DCHECK(thread_checker_.IsCurrent());
return record_parameters_;
}
} // namespace webrtc

View file

@ -0,0 +1,225 @@
/*
* Copyright (c) 2015 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_AUDIO_MANAGER_H_
#define MODULES_AUDIO_DEVICE_ANDROID_AUDIO_MANAGER_H_
#include <SLES/OpenSLES.h>
#include <jni.h>
#include <memory>
#include "api/sequence_checker.h"
#include "modules/audio_device/android/audio_common.h"
#include "modules/audio_device/android/opensles_common.h"
#include "modules/audio_device/audio_device_config.h"
#include "modules/audio_device/audio_device_generic.h"
#include "modules/audio_device/include/audio_device_defines.h"
#include "modules/utility/include/helpers_android.h"
#include "modules/utility/include/jvm_android.h"
namespace webrtc {
// Implements support for functions in the WebRTC audio stack for Android that
// relies on the AudioManager in android.media. It also populates an
// AudioParameter structure with native audio parameters detected at
// construction. This class does not make any audio-related modifications
// unless Init() is called. Caching audio parameters makes no changes but only
// reads data from the Java side.
class AudioManager {
public:
// Wraps the Java specific parts of the AudioManager into one helper class.
// Stores method IDs for all supported methods at construction and then
// allows calls like JavaAudioManager::Close() while hiding the Java/JNI
// parts that are associated with this call.
class JavaAudioManager {
public:
JavaAudioManager(NativeRegistration* native_registration,
std::unique_ptr<GlobalRef> audio_manager);
~JavaAudioManager();
bool Init();
void Close();
bool IsCommunicationModeEnabled();
bool IsDeviceBlacklistedForOpenSLESUsage();
private:
std::unique_ptr<GlobalRef> audio_manager_;
jmethodID init_;
jmethodID dispose_;
jmethodID is_communication_mode_enabled_;
jmethodID is_device_blacklisted_for_open_sles_usage_;
};
AudioManager();
~AudioManager();
// Sets the currently active audio layer combination. Must be called before
// Init().
void SetActiveAudioLayer(AudioDeviceModule::AudioLayer audio_layer);
// Creates and realizes the main (global) Open SL engine object and returns
// a reference to it. The engine object is only created at the first call
// since OpenSL ES for Android only supports a single engine per application.
// Subsequent calls returns the already created engine. The SL engine object
// is destroyed when the AudioManager object is deleted. It means that the
// engine object will be the first OpenSL ES object to be created and last
// object to be destroyed.
// Note that NULL will be returned unless the audio layer is specified as
// AudioDeviceModule::kAndroidOpenSLESAudio or
// AudioDeviceModule::kAndroidJavaInputAndOpenSLESOutputAudio.
SLObjectItf GetOpenSLEngine();
// Initializes the audio manager and stores the current audio mode.
bool Init();
// Revert any setting done by Init().
bool Close();
// Returns true if current audio mode is AudioManager.MODE_IN_COMMUNICATION.
bool IsCommunicationModeEnabled() const;
// Native audio parameters stored during construction.
const AudioParameters& GetPlayoutAudioParameters();
const AudioParameters& GetRecordAudioParameters();
// Returns true if the device supports built-in audio effects for AEC, AGC
// and NS. Some devices can also be blacklisted for use in combination with
// platform effects and these devices will return false.
// Can currently only be used in combination with a Java based audio backend
// for the recoring side (i.e. using the android.media.AudioRecord API).
bool IsAcousticEchoCancelerSupported() const;
bool IsAutomaticGainControlSupported() const;
bool IsNoiseSuppressorSupported() const;
// Returns true if the device supports the low-latency audio paths in
// combination with OpenSL ES.
bool IsLowLatencyPlayoutSupported() const;
bool IsLowLatencyRecordSupported() const;
// Returns true if the device supports (and has been configured for) stereo.
// Call the Java API WebRtcAudioManager.setStereoOutput/Input() with true as
// paramter to enable stereo. Default is mono in both directions and the
// setting is set once and for all when the audio manager object is created.
// TODO(henrika): stereo is not supported in combination with OpenSL ES.
bool IsStereoPlayoutSupported() const;
bool IsStereoRecordSupported() const;
// Returns true if the device supports pro-audio features in combination with
// OpenSL ES.
bool IsProAudioSupported() const;
// Returns true if the device supports AAudio.
bool IsAAudioSupported() const;
// Returns the estimated total delay of this device. Unit is in milliseconds.
// The vaule is set once at construction and never changes after that.
// Possible values are webrtc::kLowLatencyModeDelayEstimateInMilliseconds and
// webrtc::kHighLatencyModeDelayEstimateInMilliseconds.
int GetDelayEstimateInMilliseconds() const;
private:
// Called from Java side so we can cache the native audio parameters.
// This method will be called by the WebRtcAudioManager constructor, i.e.
// on the same thread that this object is created on.
static void JNICALL CacheAudioParameters(JNIEnv* env,
jobject obj,
jint sample_rate,
jint output_channels,
jint input_channels,
jboolean hardware_aec,
jboolean hardware_agc,
jboolean hardware_ns,
jboolean low_latency_output,
jboolean low_latency_input,
jboolean pro_audio,
jboolean a_audio,
jint output_buffer_size,
jint input_buffer_size,
jlong native_audio_manager);
void OnCacheAudioParameters(JNIEnv* env,
jint sample_rate,
jint output_channels,
jint input_channels,
jboolean hardware_aec,
jboolean hardware_agc,
jboolean hardware_ns,
jboolean low_latency_output,
jboolean low_latency_input,
jboolean pro_audio,
jboolean a_audio,
jint output_buffer_size,
jint input_buffer_size);
// Stores thread ID in the constructor.
// We can then use RTC_DCHECK_RUN_ON(&thread_checker_) to ensure that
// other methods are called from the same thread.
SequenceChecker thread_checker_;
// Calls JavaVM::AttachCurrentThread() if this thread is not attached at
// construction.
// Also ensures that DetachCurrentThread() is called at destruction.
JvmThreadConnector attach_thread_if_needed_;
// Wraps the JNI interface pointer and methods associated with it.
std::unique_ptr<JNIEnvironment> j_environment_;
// Contains factory method for creating the Java object.
std::unique_ptr<NativeRegistration> j_native_registration_;
// Wraps the Java specific parts of the AudioManager.
std::unique_ptr<AudioManager::JavaAudioManager> j_audio_manager_;
// Contains the selected audio layer specified by the AudioLayer enumerator
// in the AudioDeviceModule class.
AudioDeviceModule::AudioLayer audio_layer_;
// This object is the global entry point of the OpenSL ES API.
// After creating the engine object, the application can obtain this objects
// SLEngineItf interface. This interface contains creation methods for all
// the other object types in the API. None of these interface are realized
// by this class. It only provides access to the global engine object.
webrtc::ScopedSLObjectItf engine_object_;
// Set to true by Init() and false by Close().
bool initialized_;
// True if device supports hardware (or built-in) AEC.
bool hardware_aec_;
// True if device supports hardware (or built-in) AGC.
bool hardware_agc_;
// True if device supports hardware (or built-in) NS.
bool hardware_ns_;
// True if device supports the low-latency OpenSL ES audio path for output.
bool low_latency_playout_;
// True if device supports the low-latency OpenSL ES audio path for input.
bool low_latency_record_;
// True if device supports the low-latency OpenSL ES pro-audio path.
bool pro_audio_;
// True if device supports the low-latency AAudio audio path.
bool a_audio_;
// The delay estimate can take one of two fixed values depending on if the
// device supports low-latency output or not.
int delay_estimate_in_milliseconds_;
// Contains native parameters (e.g. sample rate, channel configuration).
// Set at construction in OnCacheAudioParameters() which is called from
// Java on the same thread as this object is created on.
AudioParameters playout_parameters_;
AudioParameters record_parameters_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_AUDIO_MANAGER_H_

View file

@ -0,0 +1,283 @@
/*
* 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 "modules/audio_device/android/audio_merged_screen_record_jni.h"
#include <string>
#include <utility>
#include "modules/audio_device/android/audio_common.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/platform_thread.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
namespace {
// Scoped class which logs its time of life as a UMA statistic. It generates
// a histogram which measures the time it takes for a method/scope to execute.
class ScopedHistogramTimer {
public:
explicit ScopedHistogramTimer(const std::string& name)
: histogram_name_(name), start_time_ms_(rtc::TimeMillis()) {}
~ScopedHistogramTimer() {
const int64_t life_time_ms = rtc::TimeSince(start_time_ms_);
RTC_HISTOGRAM_COUNTS_1000(histogram_name_, life_time_ms);
RTC_LOG(LS_INFO) << histogram_name_ << ": " << life_time_ms;
}
private:
const std::string histogram_name_;
int64_t start_time_ms_;
};
} // namespace
// AudioRecordJni::JavaAudioRecord implementation.
AudioMergedScreenRecordJni::JavaAudioRecord::JavaAudioRecord(
NativeRegistration* native_reg,
std::unique_ptr<GlobalRef> audio_record)
: audio_record_(std::move(audio_record)),
init_recording_(native_reg->GetMethodId("initRecording", "(II)I")),
start_recording_(native_reg->GetMethodId("startRecording", "()Z")),
stop_recording_(native_reg->GetMethodId("stopRecording", "()Z")),
enable_built_in_aec_(native_reg->GetMethodId("enableBuiltInAEC", "(Z)Z")),
enable_built_in_ns_(native_reg->GetMethodId("enableBuiltInNS", "(Z)Z")),
on_destroy_(native_reg->GetMethodId("onDestroy", "()V")) {}
AudioMergedScreenRecordJni::JavaAudioRecord::~JavaAudioRecord() {
audio_record_->CallVoidMethod(on_destroy_);
}
int AudioMergedScreenRecordJni::JavaAudioRecord::InitRecording(int sample_rate,
size_t channels) {
return audio_record_->CallIntMethod(init_recording_,
static_cast<jint>(sample_rate),
static_cast<jint>(channels));
}
bool AudioMergedScreenRecordJni::JavaAudioRecord::StartRecording() {
return audio_record_->CallBooleanMethod(start_recording_);
}
bool AudioMergedScreenRecordJni::JavaAudioRecord::StopRecording() {
return audio_record_->CallBooleanMethod(stop_recording_);
}
bool AudioMergedScreenRecordJni::JavaAudioRecord::EnableBuiltInAEC(bool enable) {
return audio_record_->CallBooleanMethod(enable_built_in_aec_,
static_cast<jboolean>(enable));
}
bool AudioMergedScreenRecordJni::JavaAudioRecord::EnableBuiltInNS(bool enable) {
return audio_record_->CallBooleanMethod(enable_built_in_ns_,
static_cast<jboolean>(enable));
}
// AudioRecordJni implementation.
AudioMergedScreenRecordJni::AudioMergedScreenRecordJni(AudioManager* audio_manager)
: j_environment_(JVM::GetInstance()->environment()),
audio_manager_(audio_manager),
audio_parameters_(audio_manager->GetRecordAudioParameters()),
total_delay_in_milliseconds_(0),
direct_buffer_address_(nullptr),
direct_buffer_capacity_in_bytes_(0),
frames_per_buffer_(0),
initialized_(false),
recording_(false),
audio_device_buffer_(nullptr) {
RTC_LOG(LS_INFO) << "ctor";
RTC_DCHECK(audio_parameters_.is_valid());
RTC_CHECK(j_environment_);
JNINativeMethod native_methods[] = {
{"nativeCacheDirectBufferAddress", "(Ljava/nio/ByteBuffer;J)V",
reinterpret_cast<void*>(
&webrtc::AudioMergedScreenRecordJni::CacheDirectBufferAddress)},
{"nativeDataIsRecorded", "(IJ)V",
reinterpret_cast<void*>(&webrtc::AudioMergedScreenRecordJni::DataIsRecorded)}};
j_native_registration_ = j_environment_->RegisterNatives(
"org/webrtc/voiceengine/WebRtcAudioRecord", native_methods,
arraysize(native_methods));
j_audio_record_.reset(
new JavaAudioRecord(j_native_registration_.get(),
j_native_registration_->NewObject(
"<init>", "(JI)V", PointerTojlong(this), 2)));
// Detach from this thread since we want to use the checker to verify calls
// from the Java based audio thread.
thread_checker_java_.Detach();
}
AudioMergedScreenRecordJni::~AudioMergedScreenRecordJni() {
RTC_LOG(LS_INFO) << "dtor";
RTC_DCHECK(thread_checker_.IsCurrent());
Terminate();
}
int32_t AudioMergedScreenRecordJni::Init() {
RTC_LOG(LS_INFO) << "Init";
RTC_DCHECK(thread_checker_.IsCurrent());
return 0;
}
int32_t AudioMergedScreenRecordJni::Terminate() {
RTC_LOG(LS_INFO) << "Terminate";
RTC_DCHECK(thread_checker_.IsCurrent());
StopRecording();
return 0;
}
int32_t AudioMergedScreenRecordJni::InitRecording() {
RTC_LOG(LS_INFO) << "InitRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!initialized_);
RTC_DCHECK(!recording_);
ScopedHistogramTimer timer("WebRTC.Audio.InitRecordingDurationMs");
int frames_per_buffer = j_audio_record_->InitRecording(
audio_parameters_.sample_rate(), audio_parameters_.channels());
if (frames_per_buffer < 0) {
direct_buffer_address_ = nullptr;
RTC_LOG(LS_ERROR) << "InitRecording failed";
return -1;
}
frames_per_buffer_ = static_cast<size_t>(frames_per_buffer);
RTC_LOG(LS_INFO) << "frames_per_buffer: " << frames_per_buffer_;
const size_t bytes_per_frame = audio_parameters_.channels() * sizeof(int16_t);
RTC_CHECK_EQ(direct_buffer_capacity_in_bytes_,
frames_per_buffer_ * bytes_per_frame);
RTC_CHECK_EQ(frames_per_buffer_, audio_parameters_.frames_per_10ms_buffer());
initialized_ = true;
return 0;
}
int32_t AudioMergedScreenRecordJni::StartRecording() {
RTC_LOG(LS_INFO) << "StartRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!recording_);
if (!initialized_) {
RTC_DLOG(LS_WARNING)
<< "Recording can not start since InitRecording must succeed first";
return 0;
}
ScopedHistogramTimer timer("WebRTC.Audio.StartRecordingDurationMs");
if (!j_audio_record_->StartRecording()) {
RTC_LOG(LS_ERROR) << "StartRecording failed";
return -1;
}
recording_ = true;
return 0;
}
int32_t AudioMergedScreenRecordJni::StopRecording() {
RTC_LOG(LS_INFO) << "StopRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
if (!initialized_ || !recording_) {
return 0;
}
if (!j_audio_record_->StopRecording()) {
RTC_LOG(LS_ERROR) << "StopRecording failed";
return -1;
}
// If we don't detach here, we will hit a RTC_DCHECK in OnDataIsRecorded()
// next time StartRecording() is called since it will create a new Java
// thread.
thread_checker_java_.Detach();
initialized_ = false;
recording_ = false;
direct_buffer_address_ = nullptr;
return 0;
}
void AudioMergedScreenRecordJni::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
RTC_LOG(LS_INFO) << "AttachAudioBuffer";
RTC_DCHECK(thread_checker_.IsCurrent());
audio_device_buffer_ = audioBuffer;
const int sample_rate_hz = audio_parameters_.sample_rate();
RTC_LOG(LS_INFO) << "SetRecordingSampleRate(" << sample_rate_hz << ")";
audio_device_buffer_->SetRecordingSampleRate(sample_rate_hz);
const size_t channels = audio_parameters_.channels();
RTC_LOG(LS_INFO) << "SetRecordingChannels(" << channels << ")";
audio_device_buffer_->SetRecordingChannels(channels);
total_delay_in_milliseconds_ =
audio_manager_->GetDelayEstimateInMilliseconds();
RTC_DCHECK_GT(total_delay_in_milliseconds_, 0);
RTC_LOG(LS_INFO) << "total_delay_in_milliseconds: "
<< total_delay_in_milliseconds_;
}
int32_t AudioMergedScreenRecordJni::EnableBuiltInAEC(bool enable) {
RTC_LOG(LS_INFO) << "EnableBuiltInAEC(" << enable << ")";
RTC_DCHECK(thread_checker_.IsCurrent());
return j_audio_record_->EnableBuiltInAEC(enable) ? 0 : -1;
}
int32_t AudioMergedScreenRecordJni::EnableBuiltInAGC(bool enable) {
// TODO(henrika): possibly remove when no longer used by any client.
RTC_CHECK_NOTREACHED();
}
int32_t AudioMergedScreenRecordJni::EnableBuiltInNS(bool enable) {
RTC_LOG(LS_INFO) << "EnableBuiltInNS(" << enable << ")";
RTC_DCHECK(thread_checker_.IsCurrent());
return j_audio_record_->EnableBuiltInNS(enable) ? 0 : -1;
}
JNI_FUNCTION_ALIGN
void JNICALL AudioMergedScreenRecordJni::CacheDirectBufferAddress(JNIEnv* env,
jobject obj,
jobject byte_buffer,
jlong nativeAudioRecord) {
webrtc::AudioMergedScreenRecordJni* this_object =
reinterpret_cast<webrtc::AudioMergedScreenRecordJni*>(nativeAudioRecord);
this_object->OnCacheDirectBufferAddress(env, byte_buffer);
}
void AudioMergedScreenRecordJni::OnCacheDirectBufferAddress(JNIEnv* env,
jobject byte_buffer) {
RTC_LOG(LS_INFO) << "OnCacheDirectBufferAddress";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!direct_buffer_address_);
direct_buffer_address_ = env->GetDirectBufferAddress(byte_buffer);
jlong capacity = env->GetDirectBufferCapacity(byte_buffer);
RTC_LOG(LS_INFO) << "direct buffer capacity: " << capacity;
direct_buffer_capacity_in_bytes_ = static_cast<size_t>(capacity);
}
JNI_FUNCTION_ALIGN
void JNICALL AudioMergedScreenRecordJni::DataIsRecorded(JNIEnv* env,
jobject obj,
jint length,
jlong nativeAudioRecord) {
webrtc::AudioMergedScreenRecordJni* this_object =
reinterpret_cast<webrtc::AudioMergedScreenRecordJni*>(nativeAudioRecord);
this_object->OnDataIsRecorded(length);
}
// This method is called on a high-priority thread from Java. The name of
// the thread is 'AudioRecordThread'.
void AudioMergedScreenRecordJni::OnDataIsRecorded(int length) {
RTC_DCHECK(thread_checker_java_.IsCurrent());
if (!audio_device_buffer_) {
RTC_LOG(LS_ERROR) << "AttachAudioBuffer has not been called";
return;
}
audio_device_buffer_->SetRecordedBuffer(direct_buffer_address_,
frames_per_buffer_);
// We provide one (combined) fixed delay estimate for the APM and use the
// |playDelayMs| parameter only. Components like the AEC only sees the sum
// of |playDelayMs| and |recDelayMs|, hence the distributions does not matter.
audio_device_buffer_->SetVQEData(total_delay_in_milliseconds_, 0);
if (audio_device_buffer_->DeliverRecordedData() == -1) {
RTC_LOG(LS_INFO) << "AudioDeviceBuffer::DeliverRecordedData failed";
}
}
} // namespace webrtc

View file

@ -0,0 +1,169 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_AUDIO_MERGED_SCREEN_RECORD_JNI_H_
#define MODULES_AUDIO_DEVICE_ANDROID_AUDIO_MERGED_SCREEN_RECORD_JNI_H_
#include <jni.h>
#include <memory>
#include "api/sequence_checker.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/audio_device_generic.h"
#include "modules/audio_device/include/audio_device_defines.h"
#include "modules/utility/include/helpers_android.h"
#include "modules/utility/include/jvm_android.h"
namespace webrtc {
// Implements 16-bit mono PCM audio input support for Android using the Java
// AudioRecord interface. Most of the work is done by its Java counterpart in
// WebRtcAudioRecord.java. This class is created and lives on a thread in
// C++-land, but recorded audio buffers are delivered on a high-priority
// thread managed by the Java class.
//
// The Java class makes use of AudioEffect features (mainly AEC) which are
// first available in Jelly Bean. If it is instantiated running against earlier
// SDKs, the AEC provided by the APM in WebRTC must be used and enabled
// separately instead.
//
// An instance must be created and destroyed on one and the same thread.
// All public methods must also be called on the same thread. A thread checker
// will RTC_DCHECK if any method is called on an invalid thread.
//
// This class uses JvmThreadConnector to attach to a Java VM if needed
// and detach when the object goes out of scope. Additional thread checking
// guarantees that no other (possibly non attached) thread is used.
class AudioMergedScreenRecordJni {
public:
// Wraps the Java specific parts of the AudioRecordJni into one helper class.
class JavaAudioRecord {
public:
JavaAudioRecord(NativeRegistration* native_registration,
std::unique_ptr<GlobalRef> audio_track);
~JavaAudioRecord();
int InitRecording(int sample_rate, size_t channels);
bool StartRecording();
bool StopRecording();
bool EnableBuiltInAEC(bool enable);
bool EnableBuiltInNS(bool enable);
private:
std::unique_ptr<GlobalRef> audio_record_;
jmethodID init_recording_;
jmethodID start_recording_;
jmethodID stop_recording_;
jmethodID enable_built_in_aec_;
jmethodID enable_built_in_ns_;
jmethodID on_destroy_;
};
explicit AudioMergedScreenRecordJni(AudioManager* audio_manager);
~AudioMergedScreenRecordJni();
int32_t Init();
int32_t Terminate();
int32_t InitRecording();
bool RecordingIsInitialized() const { return initialized_; }
int32_t StartRecording();
int32_t StopRecording();
bool Recording() const { return recording_; }
void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer);
int32_t EnableBuiltInAEC(bool enable);
int32_t EnableBuiltInAGC(bool enable);
int32_t EnableBuiltInNS(bool enable);
private:
// Called from Java side so we can cache the address of the Java-manged
// |byte_buffer| in |direct_buffer_address_|. The size of the buffer
// is also stored in |direct_buffer_capacity_in_bytes_|.
// This method will be called by the WebRtcAudioRecord constructor, i.e.,
// on the same thread that this object is created on.
static void JNICALL CacheDirectBufferAddress(JNIEnv* env,
jobject obj,
jobject byte_buffer,
jlong nativeAudioRecord);
void OnCacheDirectBufferAddress(JNIEnv* env, jobject byte_buffer);
// Called periodically by the Java based WebRtcAudioRecord object when
// recording has started. Each call indicates that there are |length| new
// bytes recorded in the memory area |direct_buffer_address_| and it is
// now time to send these to the consumer.
// This method is called on a high-priority thread from Java. The name of
// the thread is 'AudioRecordThread'.
static void JNICALL DataIsRecorded(JNIEnv* env,
jobject obj,
jint length,
jlong nativeAudioRecord);
void OnDataIsRecorded(int length);
// Stores thread ID in constructor.
SequenceChecker thread_checker_;
// Stores thread ID in first call to OnDataIsRecorded() from high-priority
// thread in Java. Detached during construction of this object.
SequenceChecker thread_checker_java_;
// Calls JavaVM::AttachCurrentThread() if this thread is not attached at
// construction.
// Also ensures that DetachCurrentThread() is called at destruction.
JvmThreadConnector attach_thread_if_needed_;
// Wraps the JNI interface pointer and methods associated with it.
std::unique_ptr<JNIEnvironment> j_environment_;
// Contains factory method for creating the Java object.
std::unique_ptr<NativeRegistration> j_native_registration_;
// Wraps the Java specific parts of the AudioRecordJni class.
std::unique_ptr<AudioMergedScreenRecordJni::JavaAudioRecord> j_audio_record_;
// Raw pointer to the audio manger.
const AudioManager* audio_manager_;
// Contains audio parameters provided to this class at construction by the
// AudioManager.
const AudioParameters audio_parameters_;
// Delay estimate of the total round-trip delay (input + output).
// Fixed value set once in AttachAudioBuffer() and it can take one out of two
// possible values. See audio_common.h for details.
int total_delay_in_milliseconds_;
// Cached copy of address to direct audio buffer owned by |j_audio_record_|.
void* direct_buffer_address_;
// Number of bytes in the direct audio buffer owned by |j_audio_record_|.
size_t direct_buffer_capacity_in_bytes_;
// Number audio frames per audio buffer. Each audio frame corresponds to
// one sample of PCM mono data at 16 bits per sample. Hence, each audio
// frame contains 2 bytes (given that the Java layer only supports mono).
// Example: 480 for 48000 Hz or 441 for 44100 Hz.
size_t frames_per_buffer_;
bool initialized_;
bool recording_;
// Raw pointer handle provided to us in AttachAudioBuffer(). Owned by the
// AudioDeviceModuleImpl class and called by AudioDeviceModule::Create().
AudioDeviceBuffer* audio_device_buffer_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_AUDIO_RECORD_JNI_H_

View file

@ -0,0 +1,280 @@
/*
* 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 "modules/audio_device/android/audio_record_jni.h"
#include <string>
#include <utility>
#include "modules/audio_device/android/audio_common.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/platform_thread.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
namespace {
// Scoped class which logs its time of life as a UMA statistic. It generates
// a histogram which measures the time it takes for a method/scope to execute.
class ScopedHistogramTimer {
public:
explicit ScopedHistogramTimer(const std::string& name)
: histogram_name_(name), start_time_ms_(rtc::TimeMillis()) {}
~ScopedHistogramTimer() {
const int64_t life_time_ms = rtc::TimeSince(start_time_ms_);
RTC_HISTOGRAM_COUNTS_1000(histogram_name_, life_time_ms);
RTC_LOG(LS_INFO) << histogram_name_ << ": " << life_time_ms;
}
private:
const std::string histogram_name_;
int64_t start_time_ms_;
};
} // namespace
// AudioRecordJni::JavaAudioRecord implementation.
AudioRecordJni::JavaAudioRecord::JavaAudioRecord(
NativeRegistration* native_reg,
std::unique_ptr<GlobalRef> audio_record)
: audio_record_(std::move(audio_record)),
init_recording_(native_reg->GetMethodId("initRecording", "(II)I")),
start_recording_(native_reg->GetMethodId("startRecording", "()Z")),
stop_recording_(native_reg->GetMethodId("stopRecording", "()Z")),
enable_built_in_aec_(native_reg->GetMethodId("enableBuiltInAEC", "(Z)Z")),
enable_built_in_ns_(native_reg->GetMethodId("enableBuiltInNS", "(Z)Z")) {}
AudioRecordJni::JavaAudioRecord::~JavaAudioRecord() {}
int AudioRecordJni::JavaAudioRecord::InitRecording(int sample_rate,
size_t channels) {
return audio_record_->CallIntMethod(init_recording_,
static_cast<jint>(sample_rate),
static_cast<jint>(channels));
}
bool AudioRecordJni::JavaAudioRecord::StartRecording() {
return audio_record_->CallBooleanMethod(start_recording_);
}
bool AudioRecordJni::JavaAudioRecord::StopRecording() {
return audio_record_->CallBooleanMethod(stop_recording_);
}
bool AudioRecordJni::JavaAudioRecord::EnableBuiltInAEC(bool enable) {
return audio_record_->CallBooleanMethod(enable_built_in_aec_,
static_cast<jboolean>(enable));
}
bool AudioRecordJni::JavaAudioRecord::EnableBuiltInNS(bool enable) {
return audio_record_->CallBooleanMethod(enable_built_in_ns_,
static_cast<jboolean>(enable));
}
// AudioRecordJni implementation.
AudioRecordJni::AudioRecordJni(AudioManager* audio_manager)
: j_environment_(JVM::GetInstance()->environment()),
audio_manager_(audio_manager),
audio_parameters_(audio_manager->GetRecordAudioParameters()),
total_delay_in_milliseconds_(0),
direct_buffer_address_(nullptr),
direct_buffer_capacity_in_bytes_(0),
frames_per_buffer_(0),
initialized_(false),
recording_(false),
audio_device_buffer_(nullptr) {
RTC_LOG(LS_INFO) << "ctor";
RTC_DCHECK(audio_parameters_.is_valid());
RTC_CHECK(j_environment_);
JNINativeMethod native_methods[] = {
{"nativeCacheDirectBufferAddress", "(Ljava/nio/ByteBuffer;J)V",
reinterpret_cast<void*>(
&webrtc::AudioRecordJni::CacheDirectBufferAddress)},
{"nativeDataIsRecorded", "(IJ)V",
reinterpret_cast<void*>(&webrtc::AudioRecordJni::DataIsRecorded)}};
j_native_registration_ = j_environment_->RegisterNatives(
"org/webrtc/voiceengine/WebRtcAudioRecord", native_methods,
arraysize(native_methods));
j_audio_record_.reset(
new JavaAudioRecord(j_native_registration_.get(),
j_native_registration_->NewObject(
"<init>", "(JI)V", PointerTojlong(this), 0)));
// Detach from this thread since we want to use the checker to verify calls
// from the Java based audio thread.
thread_checker_java_.Detach();
}
AudioRecordJni::~AudioRecordJni() {
RTC_LOG(LS_INFO) << "dtor";
RTC_DCHECK(thread_checker_.IsCurrent());
Terminate();
}
int32_t AudioRecordJni::Init() {
RTC_LOG(LS_INFO) << "Init";
RTC_DCHECK(thread_checker_.IsCurrent());
return 0;
}
int32_t AudioRecordJni::Terminate() {
RTC_LOG(LS_INFO) << "Terminate";
RTC_DCHECK(thread_checker_.IsCurrent());
StopRecording();
return 0;
}
int32_t AudioRecordJni::InitRecording() {
RTC_LOG(LS_INFO) << "InitRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!initialized_);
RTC_DCHECK(!recording_);
ScopedHistogramTimer timer("WebRTC.Audio.InitRecordingDurationMs");
int frames_per_buffer = j_audio_record_->InitRecording(
audio_parameters_.sample_rate(), audio_parameters_.channels());
if (frames_per_buffer < 0) {
direct_buffer_address_ = nullptr;
RTC_LOG(LS_ERROR) << "InitRecording failed";
return -1;
}
frames_per_buffer_ = static_cast<size_t>(frames_per_buffer);
RTC_LOG(LS_INFO) << "frames_per_buffer: " << frames_per_buffer_;
const size_t bytes_per_frame = audio_parameters_.channels() * sizeof(int16_t);
RTC_CHECK_EQ(direct_buffer_capacity_in_bytes_,
frames_per_buffer_ * bytes_per_frame);
RTC_CHECK_EQ(frames_per_buffer_, audio_parameters_.frames_per_10ms_buffer());
initialized_ = true;
return 0;
}
int32_t AudioRecordJni::StartRecording() {
RTC_LOG(LS_INFO) << "StartRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!recording_);
if (!initialized_) {
RTC_DLOG(LS_WARNING)
<< "Recording can not start since InitRecording must succeed first";
return 0;
}
ScopedHistogramTimer timer("WebRTC.Audio.StartRecordingDurationMs");
if (!j_audio_record_->StartRecording()) {
RTC_LOG(LS_ERROR) << "StartRecording failed";
return -1;
}
recording_ = true;
return 0;
}
int32_t AudioRecordJni::StopRecording() {
RTC_LOG(LS_INFO) << "StopRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
if (!initialized_ || !recording_) {
return 0;
}
if (!j_audio_record_->StopRecording()) {
RTC_LOG(LS_ERROR) << "StopRecording failed";
return -1;
}
// If we don't detach here, we will hit a RTC_DCHECK in OnDataIsRecorded()
// next time StartRecording() is called since it will create a new Java
// thread.
thread_checker_java_.Detach();
initialized_ = false;
recording_ = false;
direct_buffer_address_ = nullptr;
return 0;
}
void AudioRecordJni::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
RTC_LOG(LS_INFO) << "AttachAudioBuffer";
RTC_DCHECK(thread_checker_.IsCurrent());
audio_device_buffer_ = audioBuffer;
const int sample_rate_hz = audio_parameters_.sample_rate();
RTC_LOG(LS_INFO) << "SetRecordingSampleRate(" << sample_rate_hz << ")";
audio_device_buffer_->SetRecordingSampleRate(sample_rate_hz);
const size_t channels = audio_parameters_.channels();
RTC_LOG(LS_INFO) << "SetRecordingChannels(" << channels << ")";
audio_device_buffer_->SetRecordingChannels(channels);
total_delay_in_milliseconds_ =
audio_manager_->GetDelayEstimateInMilliseconds();
RTC_DCHECK_GT(total_delay_in_milliseconds_, 0);
RTC_LOG(LS_INFO) << "total_delay_in_milliseconds: "
<< total_delay_in_milliseconds_;
}
int32_t AudioRecordJni::EnableBuiltInAEC(bool enable) {
RTC_LOG(LS_INFO) << "EnableBuiltInAEC(" << enable << ")";
RTC_DCHECK(thread_checker_.IsCurrent());
return j_audio_record_->EnableBuiltInAEC(enable) ? 0 : -1;
}
int32_t AudioRecordJni::EnableBuiltInAGC(bool enable) {
// TODO(henrika): possibly remove when no longer used by any client.
RTC_CHECK_NOTREACHED();
}
int32_t AudioRecordJni::EnableBuiltInNS(bool enable) {
RTC_LOG(LS_INFO) << "EnableBuiltInNS(" << enable << ")";
RTC_DCHECK(thread_checker_.IsCurrent());
return j_audio_record_->EnableBuiltInNS(enable) ? 0 : -1;
}
JNI_FUNCTION_ALIGN
void JNICALL AudioRecordJni::CacheDirectBufferAddress(JNIEnv* env,
jobject obj,
jobject byte_buffer,
jlong nativeAudioRecord) {
webrtc::AudioRecordJni* this_object =
reinterpret_cast<webrtc::AudioRecordJni*>(nativeAudioRecord);
this_object->OnCacheDirectBufferAddress(env, byte_buffer);
}
void AudioRecordJni::OnCacheDirectBufferAddress(JNIEnv* env,
jobject byte_buffer) {
RTC_LOG(LS_INFO) << "OnCacheDirectBufferAddress";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!direct_buffer_address_);
direct_buffer_address_ = env->GetDirectBufferAddress(byte_buffer);
jlong capacity = env->GetDirectBufferCapacity(byte_buffer);
RTC_LOG(LS_INFO) << "direct buffer capacity: " << capacity;
direct_buffer_capacity_in_bytes_ = static_cast<size_t>(capacity);
}
JNI_FUNCTION_ALIGN
void JNICALL AudioRecordJni::DataIsRecorded(JNIEnv* env,
jobject obj,
jint length,
jlong nativeAudioRecord) {
webrtc::AudioRecordJni* this_object =
reinterpret_cast<webrtc::AudioRecordJni*>(nativeAudioRecord);
this_object->OnDataIsRecorded(length);
}
// This method is called on a high-priority thread from Java. The name of
// the thread is 'AudioRecordThread'.
void AudioRecordJni::OnDataIsRecorded(int length) {
RTC_DCHECK(thread_checker_java_.IsCurrent());
if (!audio_device_buffer_) {
RTC_LOG(LS_ERROR) << "AttachAudioBuffer has not been called";
return;
}
audio_device_buffer_->SetRecordedBuffer(direct_buffer_address_,
frames_per_buffer_);
// We provide one (combined) fixed delay estimate for the APM and use the
// `playDelayMs` parameter only. Components like the AEC only sees the sum
// of `playDelayMs` and `recDelayMs`, hence the distributions does not matter.
audio_device_buffer_->SetVQEData(total_delay_in_milliseconds_, 0);
if (audio_device_buffer_->DeliverRecordedData() == -1) {
RTC_LOG(LS_INFO) << "AudioDeviceBuffer::DeliverRecordedData failed";
}
}
} // namespace webrtc

View file

@ -0,0 +1,168 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_AUDIO_RECORD_JNI_H_
#define MODULES_AUDIO_DEVICE_ANDROID_AUDIO_RECORD_JNI_H_
#include <jni.h>
#include <memory>
#include "api/sequence_checker.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/audio_device_generic.h"
#include "modules/audio_device/include/audio_device_defines.h"
#include "modules/utility/include/helpers_android.h"
#include "modules/utility/include/jvm_android.h"
namespace webrtc {
// Implements 16-bit mono PCM audio input support for Android using the Java
// AudioRecord interface. Most of the work is done by its Java counterpart in
// WebRtcAudioRecord.java. This class is created and lives on a thread in
// C++-land, but recorded audio buffers are delivered on a high-priority
// thread managed by the Java class.
//
// The Java class makes use of AudioEffect features (mainly AEC) which are
// first available in Jelly Bean. If it is instantiated running against earlier
// SDKs, the AEC provided by the APM in WebRTC must be used and enabled
// separately instead.
//
// An instance must be created and destroyed on one and the same thread.
// All public methods must also be called on the same thread. A thread checker
// will RTC_DCHECK if any method is called on an invalid thread.
//
// This class uses JvmThreadConnector to attach to a Java VM if needed
// and detach when the object goes out of scope. Additional thread checking
// guarantees that no other (possibly non attached) thread is used.
class AudioRecordJni {
public:
// Wraps the Java specific parts of the AudioRecordJni into one helper class.
class JavaAudioRecord {
public:
JavaAudioRecord(NativeRegistration* native_registration,
std::unique_ptr<GlobalRef> audio_track);
~JavaAudioRecord();
int InitRecording(int sample_rate, size_t channels);
bool StartRecording();
bool StopRecording();
bool EnableBuiltInAEC(bool enable);
bool EnableBuiltInNS(bool enable);
private:
std::unique_ptr<GlobalRef> audio_record_;
jmethodID init_recording_;
jmethodID start_recording_;
jmethodID stop_recording_;
jmethodID enable_built_in_aec_;
jmethodID enable_built_in_ns_;
};
explicit AudioRecordJni(AudioManager* audio_manager);
~AudioRecordJni();
int32_t Init();
int32_t Terminate();
int32_t InitRecording();
bool RecordingIsInitialized() const { return initialized_; }
int32_t StartRecording();
int32_t StopRecording();
bool Recording() const { return recording_; }
void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer);
int32_t EnableBuiltInAEC(bool enable);
int32_t EnableBuiltInAGC(bool enable);
int32_t EnableBuiltInNS(bool enable);
private:
// Called from Java side so we can cache the address of the Java-manged
// `byte_buffer` in `direct_buffer_address_`. The size of the buffer
// is also stored in `direct_buffer_capacity_in_bytes_`.
// This method will be called by the WebRtcAudioRecord constructor, i.e.,
// on the same thread that this object is created on.
static void JNICALL CacheDirectBufferAddress(JNIEnv* env,
jobject obj,
jobject byte_buffer,
jlong nativeAudioRecord);
void OnCacheDirectBufferAddress(JNIEnv* env, jobject byte_buffer);
// Called periodically by the Java based WebRtcAudioRecord object when
// recording has started. Each call indicates that there are `length` new
// bytes recorded in the memory area `direct_buffer_address_` and it is
// now time to send these to the consumer.
// This method is called on a high-priority thread from Java. The name of
// the thread is 'AudioRecordThread'.
static void JNICALL DataIsRecorded(JNIEnv* env,
jobject obj,
jint length,
jlong nativeAudioRecord);
void OnDataIsRecorded(int length);
// Stores thread ID in constructor.
SequenceChecker thread_checker_;
// Stores thread ID in first call to OnDataIsRecorded() from high-priority
// thread in Java. Detached during construction of this object.
SequenceChecker thread_checker_java_;
// Calls JavaVM::AttachCurrentThread() if this thread is not attached at
// construction.
// Also ensures that DetachCurrentThread() is called at destruction.
JvmThreadConnector attach_thread_if_needed_;
// Wraps the JNI interface pointer and methods associated with it.
std::unique_ptr<JNIEnvironment> j_environment_;
// Contains factory method for creating the Java object.
std::unique_ptr<NativeRegistration> j_native_registration_;
// Wraps the Java specific parts of the AudioRecordJni class.
std::unique_ptr<AudioRecordJni::JavaAudioRecord> j_audio_record_;
// Raw pointer to the audio manger.
const AudioManager* audio_manager_;
// Contains audio parameters provided to this class at construction by the
// AudioManager.
const AudioParameters audio_parameters_;
// Delay estimate of the total round-trip delay (input + output).
// Fixed value set once in AttachAudioBuffer() and it can take one out of two
// possible values. See audio_common.h for details.
int total_delay_in_milliseconds_;
// Cached copy of address to direct audio buffer owned by `j_audio_record_`.
void* direct_buffer_address_;
// Number of bytes in the direct audio buffer owned by `j_audio_record_`.
size_t direct_buffer_capacity_in_bytes_;
// Number audio frames per audio buffer. Each audio frame corresponds to
// one sample of PCM mono data at 16 bits per sample. Hence, each audio
// frame contains 2 bytes (given that the Java layer only supports mono).
// Example: 480 for 48000 Hz or 441 for 44100 Hz.
size_t frames_per_buffer_;
bool initialized_;
bool recording_;
// Raw pointer handle provided to us in AttachAudioBuffer(). Owned by the
// AudioDeviceModuleImpl class and called by AudioDeviceModule::Create().
AudioDeviceBuffer* audio_device_buffer_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_AUDIO_RECORD_JNI_H_

View file

@ -0,0 +1,278 @@
/*
* 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 "modules/audio_device/android/audio_screen_record_jni.h"
#include <string>
#include <utility>
#include "modules/audio_device/android/audio_common.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/platform_thread.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
namespace {
// Scoped class which logs its time of life as a UMA statistic. It generates
// a histogram which measures the time it takes for a method/scope to execute.
class ScopedHistogramTimer {
public:
explicit ScopedHistogramTimer(const std::string& name)
: histogram_name_(name), start_time_ms_(rtc::TimeMillis()) {}
~ScopedHistogramTimer() {
const int64_t life_time_ms = rtc::TimeSince(start_time_ms_);
RTC_HISTOGRAM_COUNTS_1000(histogram_name_, life_time_ms);
RTC_LOG(LS_INFO) << histogram_name_ << ": " << life_time_ms;
}
private:
const std::string histogram_name_;
int64_t start_time_ms_;
};
} // namespace
// AudioRecordJni::JavaAudioRecord implementation.
AudioScreenRecordJni::JavaAudioRecord::JavaAudioRecord(
NativeRegistration* native_reg,
std::unique_ptr<GlobalRef> audio_record)
: audio_record_(std::move(audio_record)),
init_recording_(native_reg->GetMethodId("initRecording", "(II)I")),
start_recording_(native_reg->GetMethodId("startRecording", "()Z")),
stop_recording_(native_reg->GetMethodId("stopRecording", "()Z")),
enable_built_in_aec_(native_reg->GetMethodId("enableBuiltInAEC", "(Z)Z")),
enable_built_in_ns_(native_reg->GetMethodId("enableBuiltInNS", "(Z)Z")) {}
int AudioScreenRecordJni::JavaAudioRecord::InitRecording(int sample_rate,
size_t channels) {
return audio_record_->CallIntMethod(init_recording_,
static_cast<jint>(sample_rate),
static_cast<jint>(channels));
}
bool AudioScreenRecordJni::JavaAudioRecord::StartRecording() {
return audio_record_->CallBooleanMethod(start_recording_);
}
bool AudioScreenRecordJni::JavaAudioRecord::StopRecording() {
return audio_record_->CallBooleanMethod(stop_recording_);
}
bool AudioScreenRecordJni::JavaAudioRecord::EnableBuiltInAEC(bool enable) {
return audio_record_->CallBooleanMethod(enable_built_in_aec_,
static_cast<jboolean>(enable));
}
bool AudioScreenRecordJni::JavaAudioRecord::EnableBuiltInNS(bool enable) {
return audio_record_->CallBooleanMethod(enable_built_in_ns_,
static_cast<jboolean>(enable));
}
// AudioRecordJni implementation.
AudioScreenRecordJni::AudioScreenRecordJni(AudioManager* audio_manager)
: j_environment_(JVM::GetInstance()->environment()),
audio_manager_(audio_manager),
audio_parameters_(audio_manager->GetRecordAudioParameters()),
total_delay_in_milliseconds_(0),
direct_buffer_address_(nullptr),
direct_buffer_capacity_in_bytes_(0),
frames_per_buffer_(0),
initialized_(false),
recording_(false),
audio_device_buffer_(nullptr) {
RTC_LOG(LS_INFO) << "ctor";
RTC_DCHECK(audio_parameters_.is_valid());
RTC_CHECK(j_environment_);
JNINativeMethod native_methods[] = {
{"nativeCacheDirectBufferAddress", "(Ljava/nio/ByteBuffer;J)V",
reinterpret_cast<void*>(
&webrtc::AudioScreenRecordJni::CacheDirectBufferAddress)},
{"nativeDataIsRecorded", "(IJ)V",
reinterpret_cast<void*>(&webrtc::AudioScreenRecordJni::DataIsRecorded)}};
j_native_registration_ = j_environment_->RegisterNatives(
"org/webrtc/voiceengine/WebRtcAudioRecord", native_methods,
arraysize(native_methods));
j_audio_record_.reset(
new JavaAudioRecord(j_native_registration_.get(),
j_native_registration_->NewObject(
"<init>", "(JI)V", PointerTojlong(this), 1)));
// Detach from this thread since we want to use the checker to verify calls
// from the Java based audio thread.
thread_checker_java_.Detach();
}
AudioScreenRecordJni::~AudioScreenRecordJni() {
RTC_LOG(LS_INFO) << "dtor";
RTC_DCHECK(thread_checker_.IsCurrent());
Terminate();
}
int32_t AudioScreenRecordJni::Init() {
RTC_LOG(LS_INFO) << "Init";
RTC_DCHECK(thread_checker_.IsCurrent());
return 0;
}
int32_t AudioScreenRecordJni::Terminate() {
RTC_LOG(LS_INFO) << "Terminate";
RTC_DCHECK(thread_checker_.IsCurrent());
StopRecording();
return 0;
}
int32_t AudioScreenRecordJni::InitRecording() {
RTC_LOG(LS_INFO) << "InitRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!initialized_);
RTC_DCHECK(!recording_);
ScopedHistogramTimer timer("WebRTC.Audio.InitRecordingDurationMs");
int frames_per_buffer = j_audio_record_->InitRecording(
audio_parameters_.sample_rate(), audio_parameters_.channels());
if (frames_per_buffer < 0) {
direct_buffer_address_ = nullptr;
RTC_LOG(LS_ERROR) << "InitRecording failed";
return -1;
}
frames_per_buffer_ = static_cast<size_t>(frames_per_buffer);
RTC_LOG(LS_INFO) << "frames_per_buffer: " << frames_per_buffer_;
const size_t bytes_per_frame = audio_parameters_.channels() * sizeof(int16_t);
RTC_CHECK_EQ(direct_buffer_capacity_in_bytes_,
frames_per_buffer_ * bytes_per_frame);
RTC_CHECK_EQ(frames_per_buffer_, audio_parameters_.frames_per_10ms_buffer());
initialized_ = true;
return 0;
}
int32_t AudioScreenRecordJni::StartRecording() {
RTC_LOG(LS_INFO) << "StartRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!recording_);
if (!initialized_) {
RTC_DLOG(LS_WARNING)
<< "Recording can not start since InitRecording must succeed first";
return 0;
}
ScopedHistogramTimer timer("WebRTC.Audio.StartRecordingDurationMs");
if (!j_audio_record_->StartRecording()) {
RTC_LOG(LS_ERROR) << "StartRecording failed";
return -1;
}
recording_ = true;
return 0;
}
int32_t AudioScreenRecordJni::StopRecording() {
RTC_LOG(LS_INFO) << "StopRecording";
RTC_DCHECK(thread_checker_.IsCurrent());
if (!initialized_ || !recording_) {
return 0;
}
if (!j_audio_record_->StopRecording()) {
RTC_LOG(LS_ERROR) << "StopRecording failed";
return -1;
}
// If we don't detach here, we will hit a RTC_DCHECK in OnDataIsRecorded()
// next time StartRecording() is called since it will create a new Java
// thread.
thread_checker_java_.Detach();
initialized_ = false;
recording_ = false;
direct_buffer_address_ = nullptr;
return 0;
}
void AudioScreenRecordJni::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
RTC_LOG(LS_INFO) << "AttachAudioBuffer";
RTC_DCHECK(thread_checker_.IsCurrent());
audio_device_buffer_ = audioBuffer;
const int sample_rate_hz = audio_parameters_.sample_rate();
RTC_LOG(LS_INFO) << "SetRecordingSampleRate(" << sample_rate_hz << ")";
audio_device_buffer_->SetRecordingSampleRate(sample_rate_hz);
const size_t channels = audio_parameters_.channels();
RTC_LOG(LS_INFO) << "SetRecordingChannels(" << channels << ")";
audio_device_buffer_->SetRecordingChannels(channels);
total_delay_in_milliseconds_ =
audio_manager_->GetDelayEstimateInMilliseconds();
RTC_DCHECK_GT(total_delay_in_milliseconds_, 0);
RTC_LOG(LS_INFO) << "total_delay_in_milliseconds: "
<< total_delay_in_milliseconds_;
}
int32_t AudioScreenRecordJni::EnableBuiltInAEC(bool enable) {
RTC_LOG(LS_INFO) << "EnableBuiltInAEC(" << enable << ")";
RTC_DCHECK(thread_checker_.IsCurrent());
return j_audio_record_->EnableBuiltInAEC(enable) ? 0 : -1;
}
int32_t AudioScreenRecordJni::EnableBuiltInAGC(bool enable) {
// TODO(henrika): possibly remove when no longer used by any client.
RTC_CHECK_NOTREACHED();
}
int32_t AudioScreenRecordJni::EnableBuiltInNS(bool enable) {
RTC_LOG(LS_INFO) << "EnableBuiltInNS(" << enable << ")";
RTC_DCHECK(thread_checker_.IsCurrent());
return j_audio_record_->EnableBuiltInNS(enable) ? 0 : -1;
}
JNI_FUNCTION_ALIGN
void JNICALL AudioScreenRecordJni::CacheDirectBufferAddress(JNIEnv* env,
jobject obj,
jobject byte_buffer,
jlong nativeAudioRecord) {
webrtc::AudioScreenRecordJni* this_object =
reinterpret_cast<webrtc::AudioScreenRecordJni*>(nativeAudioRecord);
this_object->OnCacheDirectBufferAddress(env, byte_buffer);
}
void AudioScreenRecordJni::OnCacheDirectBufferAddress(JNIEnv* env,
jobject byte_buffer) {
RTC_LOG(LS_INFO) << "OnCacheDirectBufferAddress";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!direct_buffer_address_);
direct_buffer_address_ = env->GetDirectBufferAddress(byte_buffer);
jlong capacity = env->GetDirectBufferCapacity(byte_buffer);
RTC_LOG(LS_INFO) << "direct buffer capacity: " << capacity;
direct_buffer_capacity_in_bytes_ = static_cast<size_t>(capacity);
}
JNI_FUNCTION_ALIGN
void JNICALL AudioScreenRecordJni::DataIsRecorded(JNIEnv* env,
jobject obj,
jint length,
jlong nativeAudioRecord) {
webrtc::AudioScreenRecordJni* this_object =
reinterpret_cast<webrtc::AudioScreenRecordJni*>(nativeAudioRecord);
this_object->OnDataIsRecorded(length);
}
// This method is called on a high-priority thread from Java. The name of
// the thread is 'AudioRecordThread'.
void AudioScreenRecordJni::OnDataIsRecorded(int length) {
RTC_DCHECK(thread_checker_java_.IsCurrent());
if (!audio_device_buffer_) {
RTC_LOG(LS_ERROR) << "AttachAudioBuffer has not been called";
return;
}
audio_device_buffer_->SetRecordedBuffer(direct_buffer_address_,
frames_per_buffer_);
// We provide one (combined) fixed delay estimate for the APM and use the
// |playDelayMs| parameter only. Components like the AEC only sees the sum
// of |playDelayMs| and |recDelayMs|, hence the distributions does not matter.
audio_device_buffer_->SetVQEData(total_delay_in_milliseconds_, 0);
if (audio_device_buffer_->DeliverRecordedData() == -1) {
RTC_LOG(LS_INFO) << "AudioDeviceBuffer::DeliverRecordedData failed";
}
}
} // namespace webrtc

View file

@ -0,0 +1,167 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_AUDIO_SCREEN_RECORD_JNI_H_
#define MODULES_AUDIO_DEVICE_ANDROID_AUDIO_SCREEN_RECORD_JNI_H_
#include <jni.h>
#include <memory>
#include "api/sequence_checker.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/audio_device_generic.h"
#include "modules/audio_device/include/audio_device_defines.h"
#include "modules/utility/include/helpers_android.h"
#include "modules/utility/include/jvm_android.h"
namespace webrtc {
// Implements 16-bit mono PCM audio input support for Android using the Java
// AudioRecord interface. Most of the work is done by its Java counterpart in
// WebRtcAudioRecord.java. This class is created and lives on a thread in
// C++-land, but recorded audio buffers are delivered on a high-priority
// thread managed by the Java class.
//
// The Java class makes use of AudioEffect features (mainly AEC) which are
// first available in Jelly Bean. If it is instantiated running against earlier
// SDKs, the AEC provided by the APM in WebRTC must be used and enabled
// separately instead.
//
// An instance must be created and destroyed on one and the same thread.
// All public methods must also be called on the same thread. A thread checker
// will RTC_DCHECK if any method is called on an invalid thread.
//
// This class uses JvmThreadConnector to attach to a Java VM if needed
// and detach when the object goes out of scope. Additional thread checking
// guarantees that no other (possibly non attached) thread is used.
class AudioScreenRecordJni {
public:
// Wraps the Java specific parts of the AudioRecordJni into one helper class.
class JavaAudioRecord {
public:
JavaAudioRecord(NativeRegistration* native_registration,
std::unique_ptr<GlobalRef> audio_track);
int InitRecording(int sample_rate, size_t channels);
bool StartRecording();
bool StopRecording();
bool EnableBuiltInAEC(bool enable);
bool EnableBuiltInNS(bool enable);
private:
std::unique_ptr<GlobalRef> audio_record_;
jmethodID init_recording_;
jmethodID start_recording_;
jmethodID stop_recording_;
jmethodID enable_built_in_aec_;
jmethodID enable_built_in_ns_;
};
explicit AudioScreenRecordJni(AudioManager* audio_manager);
~AudioScreenRecordJni();
int32_t Init();
int32_t Terminate();
int32_t InitRecording();
bool RecordingIsInitialized() const { return initialized_; }
int32_t StartRecording();
int32_t StopRecording();
bool Recording() const { return recording_; }
void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer);
int32_t EnableBuiltInAEC(bool enable);
int32_t EnableBuiltInAGC(bool enable);
int32_t EnableBuiltInNS(bool enable);
private:
// Called from Java side so we can cache the address of the Java-manged
// |byte_buffer| in |direct_buffer_address_|. The size of the buffer
// is also stored in |direct_buffer_capacity_in_bytes_|.
// This method will be called by the WebRtcAudioRecord constructor, i.e.,
// on the same thread that this object is created on.
static void JNICALL CacheDirectBufferAddress(JNIEnv* env,
jobject obj,
jobject byte_buffer,
jlong nativeAudioRecord);
void OnCacheDirectBufferAddress(JNIEnv* env, jobject byte_buffer);
// Called periodically by the Java based WebRtcAudioRecord object when
// recording has started. Each call indicates that there are |length| new
// bytes recorded in the memory area |direct_buffer_address_| and it is
// now time to send these to the consumer.
// This method is called on a high-priority thread from Java. The name of
// the thread is 'AudioRecordThread'.
static void JNICALL DataIsRecorded(JNIEnv* env,
jobject obj,
jint length,
jlong nativeAudioRecord);
void OnDataIsRecorded(int length);
// Stores thread ID in constructor.
SequenceChecker thread_checker_;
// Stores thread ID in first call to OnDataIsRecorded() from high-priority
// thread in Java. Detached during construction of this object.
SequenceChecker thread_checker_java_;
// Calls JavaVM::AttachCurrentThread() if this thread is not attached at
// construction.
// Also ensures that DetachCurrentThread() is called at destruction.
JvmThreadConnector attach_thread_if_needed_;
// Wraps the JNI interface pointer and methods associated with it.
std::unique_ptr<JNIEnvironment> j_environment_;
// Contains factory method for creating the Java object.
std::unique_ptr<NativeRegistration> j_native_registration_;
// Wraps the Java specific parts of the AudioRecordJni class.
std::unique_ptr<AudioScreenRecordJni::JavaAudioRecord> j_audio_record_;
// Raw pointer to the audio manger.
const AudioManager* audio_manager_;
// Contains audio parameters provided to this class at construction by the
// AudioManager.
const AudioParameters audio_parameters_;
// Delay estimate of the total round-trip delay (input + output).
// Fixed value set once in AttachAudioBuffer() and it can take one out of two
// possible values. See audio_common.h for details.
int total_delay_in_milliseconds_;
// Cached copy of address to direct audio buffer owned by |j_audio_record_|.
void* direct_buffer_address_;
// Number of bytes in the direct audio buffer owned by |j_audio_record_|.
size_t direct_buffer_capacity_in_bytes_;
// Number audio frames per audio buffer. Each audio frame corresponds to
// one sample of PCM mono data at 16 bits per sample. Hence, each audio
// frame contains 2 bytes (given that the Java layer only supports mono).
// Example: 480 for 48000 Hz or 441 for 44100 Hz.
size_t frames_per_buffer_;
bool initialized_;
bool recording_;
// Raw pointer handle provided to us in AttachAudioBuffer(). Owned by the
// AudioDeviceModuleImpl class and called by AudioDeviceModule::Create().
AudioDeviceBuffer* audio_device_buffer_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_AUDIO_RECORD_JNI_H_

View file

@ -0,0 +1,296 @@
/*
* 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 "modules/audio_device/android/audio_track_jni.h"
#include <utility>
#include "modules/audio_device/android/audio_manager.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/platform_thread.h"
#include "system_wrappers/include/field_trial.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
// AudioTrackJni::JavaAudioTrack implementation.
AudioTrackJni::JavaAudioTrack::JavaAudioTrack(
NativeRegistration* native_reg,
std::unique_ptr<GlobalRef> audio_track)
: audio_track_(std::move(audio_track)),
init_playout_(native_reg->GetMethodId("initPlayout", "(IID)I")),
start_playout_(native_reg->GetMethodId("startPlayout", "()Z")),
stop_playout_(native_reg->GetMethodId("stopPlayout", "()Z")),
set_stream_volume_(native_reg->GetMethodId("setStreamVolume", "(I)Z")),
get_stream_max_volume_(
native_reg->GetMethodId("getStreamMaxVolume", "()I")),
get_stream_volume_(native_reg->GetMethodId("getStreamVolume", "()I")),
get_buffer_size_in_frames_(
native_reg->GetMethodId("getBufferSizeInFrames", "()I")) {}
AudioTrackJni::JavaAudioTrack::~JavaAudioTrack() {}
bool AudioTrackJni::JavaAudioTrack::InitPlayout(int sample_rate, int channels) {
double buffer_size_factor =
strtod(webrtc::field_trial::FindFullName(
"WebRTC-AudioDevicePlayoutBufferSizeFactor")
.c_str(),
nullptr);
if (buffer_size_factor == 0)
buffer_size_factor = 1.0;
int requested_buffer_size_bytes = audio_track_->CallIntMethod(
init_playout_, sample_rate, channels, buffer_size_factor);
// Update UMA histograms for both the requested and actual buffer size.
if (requested_buffer_size_bytes >= 0) {
// To avoid division by zero, we assume the sample rate is 48k if an invalid
// value is found.
sample_rate = sample_rate <= 0 ? 48000 : sample_rate;
// This calculation assumes that audio is mono.
const int requested_buffer_size_ms =
(requested_buffer_size_bytes * 1000) / (2 * sample_rate);
RTC_HISTOGRAM_COUNTS("WebRTC.Audio.AndroidNativeRequestedAudioBufferSizeMs",
requested_buffer_size_ms, 0, 1000, 100);
int actual_buffer_size_frames =
audio_track_->CallIntMethod(get_buffer_size_in_frames_);
if (actual_buffer_size_frames >= 0) {
const int actual_buffer_size_ms =
actual_buffer_size_frames * 1000 / sample_rate;
RTC_HISTOGRAM_COUNTS("WebRTC.Audio.AndroidNativeAudioBufferSizeMs",
actual_buffer_size_ms, 0, 1000, 100);
}
return true;
}
return false;
}
bool AudioTrackJni::JavaAudioTrack::StartPlayout() {
return audio_track_->CallBooleanMethod(start_playout_);
}
bool AudioTrackJni::JavaAudioTrack::StopPlayout() {
return audio_track_->CallBooleanMethod(stop_playout_);
}
bool AudioTrackJni::JavaAudioTrack::SetStreamVolume(int volume) {
return audio_track_->CallBooleanMethod(set_stream_volume_, volume);
}
int AudioTrackJni::JavaAudioTrack::GetStreamMaxVolume() {
return audio_track_->CallIntMethod(get_stream_max_volume_);
}
int AudioTrackJni::JavaAudioTrack::GetStreamVolume() {
return audio_track_->CallIntMethod(get_stream_volume_);
}
// TODO(henrika): possible extend usage of AudioManager and add it as member.
AudioTrackJni::AudioTrackJni(AudioManager* audio_manager)
: j_environment_(JVM::GetInstance()->environment()),
audio_parameters_(audio_manager->GetPlayoutAudioParameters()),
direct_buffer_address_(nullptr),
direct_buffer_capacity_in_bytes_(0),
frames_per_buffer_(0),
initialized_(false),
playing_(false),
audio_device_buffer_(nullptr) {
RTC_LOG(LS_INFO) << "ctor";
RTC_DCHECK(audio_parameters_.is_valid());
RTC_CHECK(j_environment_);
JNINativeMethod native_methods[] = {
{"nativeCacheDirectBufferAddress", "(Ljava/nio/ByteBuffer;J)V",
reinterpret_cast<void*>(
&webrtc::AudioTrackJni::CacheDirectBufferAddress)},
{"nativeGetPlayoutData", "(IJ)V",
reinterpret_cast<void*>(&webrtc::AudioTrackJni::GetPlayoutData)}};
j_native_registration_ = j_environment_->RegisterNatives(
"org/webrtc/voiceengine/WebRtcAudioTrack", native_methods,
arraysize(native_methods));
j_audio_track_.reset(
new JavaAudioTrack(j_native_registration_.get(),
j_native_registration_->NewObject(
"<init>", "(J)V", PointerTojlong(this))));
// Detach from this thread since we want to use the checker to verify calls
// from the Java based audio thread.
thread_checker_java_.Detach();
}
AudioTrackJni::~AudioTrackJni() {
RTC_LOG(LS_INFO) << "dtor";
RTC_DCHECK(thread_checker_.IsCurrent());
Terminate();
}
int32_t AudioTrackJni::Init() {
RTC_LOG(LS_INFO) << "Init";
RTC_DCHECK(thread_checker_.IsCurrent());
return 0;
}
int32_t AudioTrackJni::Terminate() {
RTC_LOG(LS_INFO) << "Terminate";
RTC_DCHECK(thread_checker_.IsCurrent());
StopPlayout();
return 0;
}
int32_t AudioTrackJni::InitPlayout() {
RTC_LOG(LS_INFO) << "InitPlayout";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!initialized_);
RTC_DCHECK(!playing_);
if (!j_audio_track_->InitPlayout(audio_parameters_.sample_rate(),
audio_parameters_.channels())) {
RTC_LOG(LS_ERROR) << "InitPlayout failed";
return -1;
}
initialized_ = true;
return 0;
}
int32_t AudioTrackJni::StartPlayout() {
RTC_LOG(LS_INFO) << "StartPlayout";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!playing_);
if (!initialized_) {
RTC_DLOG(LS_WARNING)
<< "Playout can not start since InitPlayout must succeed first";
return 0;
}
if (!j_audio_track_->StartPlayout()) {
RTC_LOG(LS_ERROR) << "StartPlayout failed";
return -1;
}
playing_ = true;
return 0;
}
int32_t AudioTrackJni::StopPlayout() {
RTC_LOG(LS_INFO) << "StopPlayout";
RTC_DCHECK(thread_checker_.IsCurrent());
if (!initialized_ || !playing_ || j_audio_track_ == nullptr) {
return 0;
}
if (!j_audio_track_->StopPlayout()) {
RTC_LOG(LS_ERROR) << "StopPlayout failed";
return -1;
}
// If we don't detach here, we will hit a RTC_DCHECK in OnDataIsRecorded()
// next time StartRecording() is called since it will create a new Java
// thread.
thread_checker_java_.Detach();
initialized_ = false;
playing_ = false;
direct_buffer_address_ = nullptr;
return 0;
}
int AudioTrackJni::SpeakerVolumeIsAvailable(bool& available) {
available = true;
return 0;
}
int AudioTrackJni::SetSpeakerVolume(uint32_t volume) {
RTC_LOG(LS_INFO) << "SetSpeakerVolume(" << volume << ")";
RTC_DCHECK(thread_checker_.IsCurrent());
return j_audio_track_->SetStreamVolume(volume) ? 0 : -1;
}
int AudioTrackJni::MaxSpeakerVolume(uint32_t& max_volume) const {
RTC_DCHECK(thread_checker_.IsCurrent());
max_volume = j_audio_track_->GetStreamMaxVolume();
return 0;
}
int AudioTrackJni::MinSpeakerVolume(uint32_t& min_volume) const {
RTC_DCHECK(thread_checker_.IsCurrent());
min_volume = 0;
return 0;
}
int AudioTrackJni::SpeakerVolume(uint32_t& volume) const {
RTC_DCHECK(thread_checker_.IsCurrent());
volume = j_audio_track_->GetStreamVolume();
RTC_LOG(LS_INFO) << "SpeakerVolume: " << volume;
return 0;
}
// TODO(henrika): possibly add stereo support.
void AudioTrackJni::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
RTC_LOG(LS_INFO) << "AttachAudioBuffer";
RTC_DCHECK(thread_checker_.IsCurrent());
audio_device_buffer_ = audioBuffer;
const int sample_rate_hz = audio_parameters_.sample_rate();
RTC_LOG(LS_INFO) << "SetPlayoutSampleRate(" << sample_rate_hz << ")";
audio_device_buffer_->SetPlayoutSampleRate(sample_rate_hz);
const size_t channels = audio_parameters_.channels();
RTC_LOG(LS_INFO) << "SetPlayoutChannels(" << channels << ")";
audio_device_buffer_->SetPlayoutChannels(channels);
}
JNI_FUNCTION_ALIGN
void JNICALL AudioTrackJni::CacheDirectBufferAddress(JNIEnv* env,
jobject obj,
jobject byte_buffer,
jlong nativeAudioTrack) {
webrtc::AudioTrackJni* this_object =
reinterpret_cast<webrtc::AudioTrackJni*>(nativeAudioTrack);
this_object->OnCacheDirectBufferAddress(env, byte_buffer);
}
void AudioTrackJni::OnCacheDirectBufferAddress(JNIEnv* env,
jobject byte_buffer) {
RTC_LOG(LS_INFO) << "OnCacheDirectBufferAddress";
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!direct_buffer_address_);
direct_buffer_address_ = env->GetDirectBufferAddress(byte_buffer);
jlong capacity = env->GetDirectBufferCapacity(byte_buffer);
RTC_LOG(LS_INFO) << "direct buffer capacity: " << capacity;
direct_buffer_capacity_in_bytes_ = static_cast<size_t>(capacity);
const size_t bytes_per_frame = audio_parameters_.channels() * sizeof(int16_t);
frames_per_buffer_ = direct_buffer_capacity_in_bytes_ / bytes_per_frame;
RTC_LOG(LS_INFO) << "frames_per_buffer: " << frames_per_buffer_;
}
JNI_FUNCTION_ALIGN
void JNICALL AudioTrackJni::GetPlayoutData(JNIEnv* env,
jobject obj,
jint length,
jlong nativeAudioTrack) {
webrtc::AudioTrackJni* this_object =
reinterpret_cast<webrtc::AudioTrackJni*>(nativeAudioTrack);
this_object->OnGetPlayoutData(static_cast<size_t>(length));
}
// This method is called on a high-priority thread from Java. The name of
// the thread is 'AudioRecordTrack'.
void AudioTrackJni::OnGetPlayoutData(size_t length) {
RTC_DCHECK(thread_checker_java_.IsCurrent());
const size_t bytes_per_frame = audio_parameters_.channels() * sizeof(int16_t);
RTC_DCHECK_EQ(frames_per_buffer_, length / bytes_per_frame);
if (!audio_device_buffer_) {
RTC_LOG(LS_ERROR) << "AttachAudioBuffer has not been called";
return;
}
// Pull decoded data (in 16-bit PCM format) from jitter buffer.
int samples = audio_device_buffer_->RequestPlayoutData(frames_per_buffer_);
if (samples <= 0) {
RTC_LOG(LS_ERROR) << "AudioDeviceBuffer::RequestPlayoutData failed";
return;
}
RTC_DCHECK_EQ(samples, frames_per_buffer_);
// Copy decoded data into common byte buffer to ensure that it can be
// written to the Java based audio track.
samples = audio_device_buffer_->GetPlayoutData(direct_buffer_address_);
RTC_DCHECK_EQ(length, bytes_per_frame * samples);
}
} // namespace webrtc

View file

@ -0,0 +1,161 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_AUDIO_TRACK_JNI_H_
#define MODULES_AUDIO_DEVICE_ANDROID_AUDIO_TRACK_JNI_H_
#include <jni.h>
#include <memory>
#include "api/sequence_checker.h"
#include "modules/audio_device/android/audio_common.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/audio_device_generic.h"
#include "modules/audio_device/include/audio_device_defines.h"
#include "modules/utility/include/helpers_android.h"
#include "modules/utility/include/jvm_android.h"
namespace webrtc {
// Implements 16-bit mono PCM audio output support for Android using the Java
// AudioTrack interface. Most of the work is done by its Java counterpart in
// WebRtcAudioTrack.java. This class is created and lives on a thread in
// C++-land, but decoded audio buffers are requested on a high-priority
// thread managed by the Java class.
//
// An instance must be created and destroyed on one and the same thread.
// All public methods must also be called on the same thread. A thread checker
// will RTC_DCHECK if any method is called on an invalid thread.
//
// This class uses JvmThreadConnector to attach to a Java VM if needed
// and detach when the object goes out of scope. Additional thread checking
// guarantees that no other (possibly non attached) thread is used.
class AudioTrackJni {
public:
// Wraps the Java specific parts of the AudioTrackJni into one helper class.
class JavaAudioTrack {
public:
JavaAudioTrack(NativeRegistration* native_registration,
std::unique_ptr<GlobalRef> audio_track);
~JavaAudioTrack();
bool InitPlayout(int sample_rate, int channels);
bool StartPlayout();
bool StopPlayout();
bool SetStreamVolume(int volume);
int GetStreamMaxVolume();
int GetStreamVolume();
private:
std::unique_ptr<GlobalRef> audio_track_;
jmethodID init_playout_;
jmethodID start_playout_;
jmethodID stop_playout_;
jmethodID set_stream_volume_;
jmethodID get_stream_max_volume_;
jmethodID get_stream_volume_;
jmethodID get_buffer_size_in_frames_;
};
explicit AudioTrackJni(AudioManager* audio_manager);
~AudioTrackJni();
int32_t Init();
int32_t Terminate();
int32_t InitPlayout();
bool PlayoutIsInitialized() const { return initialized_; }
int32_t StartPlayout();
int32_t StopPlayout();
bool Playing() const { return playing_; }
int SpeakerVolumeIsAvailable(bool& available);
int SetSpeakerVolume(uint32_t volume);
int SpeakerVolume(uint32_t& volume) const;
int MaxSpeakerVolume(uint32_t& max_volume) const;
int MinSpeakerVolume(uint32_t& min_volume) const;
void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer);
private:
// Called from Java side so we can cache the address of the Java-manged
// `byte_buffer` in `direct_buffer_address_`. The size of the buffer
// is also stored in `direct_buffer_capacity_in_bytes_`.
// Called on the same thread as the creating thread.
static void JNICALL CacheDirectBufferAddress(JNIEnv* env,
jobject obj,
jobject byte_buffer,
jlong nativeAudioTrack);
void OnCacheDirectBufferAddress(JNIEnv* env, jobject byte_buffer);
// Called periodically by the Java based WebRtcAudioTrack object when
// playout has started. Each call indicates that `length` new bytes should
// be written to the memory area `direct_buffer_address_` for playout.
// This method is called on a high-priority thread from Java. The name of
// the thread is 'AudioTrackThread'.
static void JNICALL GetPlayoutData(JNIEnv* env,
jobject obj,
jint length,
jlong nativeAudioTrack);
void OnGetPlayoutData(size_t length);
// Stores thread ID in constructor.
SequenceChecker thread_checker_;
// Stores thread ID in first call to OnGetPlayoutData() from high-priority
// thread in Java. Detached during construction of this object.
SequenceChecker thread_checker_java_;
// Calls JavaVM::AttachCurrentThread() if this thread is not attached at
// construction.
// Also ensures that DetachCurrentThread() is called at destruction.
JvmThreadConnector attach_thread_if_needed_;
// Wraps the JNI interface pointer and methods associated with it.
std::unique_ptr<JNIEnvironment> j_environment_;
// Contains factory method for creating the Java object.
std::unique_ptr<NativeRegistration> j_native_registration_;
// Wraps the Java specific parts of the AudioTrackJni class.
std::unique_ptr<AudioTrackJni::JavaAudioTrack> j_audio_track_;
// Contains audio parameters provided to this class at construction by the
// AudioManager.
const AudioParameters audio_parameters_;
// Cached copy of address to direct audio buffer owned by `j_audio_track_`.
void* direct_buffer_address_;
// Number of bytes in the direct audio buffer owned by `j_audio_track_`.
size_t direct_buffer_capacity_in_bytes_;
// Number of audio frames per audio buffer. Each audio frame corresponds to
// one sample of PCM mono data at 16 bits per sample. Hence, each audio
// frame contains 2 bytes (given that the Java layer only supports mono).
// Example: 480 for 48000 Hz or 441 for 44100 Hz.
size_t frames_per_buffer_;
bool initialized_;
bool playing_;
// Raw pointer handle provided to us in AttachAudioBuffer(). Owned by the
// AudioDeviceModuleImpl class and called by AudioDeviceModule::Create().
// The AudioDeviceBuffer is a member of the AudioDeviceModuleImpl instance
// and therefore outlives this object.
AudioDeviceBuffer* audio_device_buffer_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_AUDIO_TRACK_JNI_H_

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2015 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 "modules/audio_device/android/build_info.h"
#include "modules/utility/include/helpers_android.h"
namespace webrtc {
BuildInfo::BuildInfo()
: j_environment_(JVM::GetInstance()->environment()),
j_build_info_(
JVM::GetInstance()->GetClass("org/webrtc/voiceengine/BuildInfo")) {}
std::string BuildInfo::GetStringFromJava(const char* name) {
jmethodID id = j_build_info_.GetStaticMethodId(name, "()Ljava/lang/String;");
jstring j_string =
static_cast<jstring>(j_build_info_.CallStaticObjectMethod(id));
return j_environment_->JavaToStdString(j_string);
}
std::string BuildInfo::GetDeviceModel() {
return GetStringFromJava("getDeviceModel");
}
std::string BuildInfo::GetBrand() {
return GetStringFromJava("getBrand");
}
std::string BuildInfo::GetDeviceManufacturer() {
return GetStringFromJava("getDeviceManufacturer");
}
std::string BuildInfo::GetAndroidBuildId() {
return GetStringFromJava("getAndroidBuildId");
}
std::string BuildInfo::GetBuildType() {
return GetStringFromJava("getBuildType");
}
std::string BuildInfo::GetBuildRelease() {
return GetStringFromJava("getBuildRelease");
}
SdkCode BuildInfo::GetSdkVersion() {
jmethodID id = j_build_info_.GetStaticMethodId("getSdkVersion", "()I");
jint j_version = j_build_info_.CallStaticIntMethod(id);
return static_cast<SdkCode>(j_version);
}
} // namespace webrtc

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2015 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_BUILD_INFO_H_
#define MODULES_AUDIO_DEVICE_ANDROID_BUILD_INFO_H_
#include <jni.h>
#include <memory>
#include <string>
#include "modules/utility/include/jvm_android.h"
namespace webrtc {
// This enumeration maps to the values returned by BuildInfo::GetSdkVersion(),
// indicating the Android release associated with a given SDK version.
// See https://developer.android.com/guide/topics/manifest/uses-sdk-element.html
// for details.
enum SdkCode {
SDK_CODE_JELLY_BEAN = 16, // Android 4.1
SDK_CODE_JELLY_BEAN_MR1 = 17, // Android 4.2
SDK_CODE_JELLY_BEAN_MR2 = 18, // Android 4.3
SDK_CODE_KITKAT = 19, // Android 4.4
SDK_CODE_WATCH = 20, // Android 4.4W
SDK_CODE_LOLLIPOP = 21, // Android 5.0
SDK_CODE_LOLLIPOP_MR1 = 22, // Android 5.1
SDK_CODE_MARSHMALLOW = 23, // Android 6.0
SDK_CODE_N = 24,
};
// Utility class used to query the Java class (org/webrtc/voiceengine/BuildInfo)
// for device and Android build information.
// The calling thread is attached to the JVM at construction if needed and a
// valid Java environment object is also created.
// All Get methods must be called on the creating thread. If not, the code will
// hit RTC_DCHECKs when calling JNIEnvironment::JavaToStdString().
class BuildInfo {
public:
BuildInfo();
~BuildInfo() {}
// End-user-visible name for the end product (e.g. "Nexus 6").
std::string GetDeviceModel();
// Consumer-visible brand (e.g. "google").
std::string GetBrand();
// Manufacturer of the product/hardware (e.g. "motorola").
std::string GetDeviceManufacturer();
// Android build ID (e.g. LMY47D).
std::string GetAndroidBuildId();
// The type of build (e.g. "user" or "eng").
std::string GetBuildType();
// The user-visible version string (e.g. "5.1").
std::string GetBuildRelease();
// The user-visible SDK version of the framework (e.g. 21). See SdkCode enum
// for translation.
SdkCode GetSdkVersion();
private:
// Helper method which calls a static getter method with `name` and returns
// a string from Java.
std::string GetStringFromJava(const char* name);
// Ensures that this class can access a valid JNI interface pointer even
// if the creating thread was not attached to the JVM.
JvmThreadConnector attach_thread_if_needed_;
// Provides access to the JNIEnv interface pointer and the JavaToStdString()
// method which is used to translate Java strings to std strings.
std::unique_ptr<JNIEnvironment> j_environment_;
// Holds the jclass object and provides access to CallStaticObjectMethod().
// Used by GetStringFromJava() during construction only.
JavaClass j_build_info_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_BUILD_INFO_H_

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2015 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 "modules/audio_device/android/ensure_initialized.h"
#include <jni.h>
#include <pthread.h>
#include <stddef.h>
#include "modules/utility/include/jvm_android.h"
#include "rtc_base/checks.h"
#include "sdk/android/src/jni/jvm.h"
namespace webrtc {
namespace audiodevicemodule {
static pthread_once_t g_initialize_once = PTHREAD_ONCE_INIT;
void EnsureInitializedOnce() {
RTC_CHECK(::webrtc::jni::GetJVM() != nullptr);
JNIEnv* jni = ::webrtc::jni::AttachCurrentThreadIfNeeded();
JavaVM* jvm = NULL;
RTC_CHECK_EQ(0, jni->GetJavaVM(&jvm));
// Initialize the Java environment (currently only used by the audio manager).
webrtc::JVM::Initialize(jvm);
}
void EnsureInitialized() {
RTC_CHECK_EQ(0, pthread_once(&g_initialize_once, &EnsureInitializedOnce));
}
} // namespace audiodevicemodule
} // namespace webrtc

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2015 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.
*/
namespace webrtc {
namespace audiodevicemodule {
void EnsureInitialized();
} // namespace audiodevicemodule
} // namespace webrtc

View file

@ -0,0 +1,103 @@
/*
* 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 "modules/audio_device/android/opensles_common.h"
#include <SLES/OpenSLES.h>
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
namespace webrtc {
// Returns a string representation given an integer SL_RESULT_XXX code.
// The mapping can be found in <SLES/OpenSLES.h>.
const char* GetSLErrorString(size_t code) {
static const char* sl_error_strings[] = {
"SL_RESULT_SUCCESS", // 0
"SL_RESULT_PRECONDITIONS_VIOLATED", // 1
"SL_RESULT_PARAMETER_INVALID", // 2
"SL_RESULT_MEMORY_FAILURE", // 3
"SL_RESULT_RESOURCE_ERROR", // 4
"SL_RESULT_RESOURCE_LOST", // 5
"SL_RESULT_IO_ERROR", // 6
"SL_RESULT_BUFFER_INSUFFICIENT", // 7
"SL_RESULT_CONTENT_CORRUPTED", // 8
"SL_RESULT_CONTENT_UNSUPPORTED", // 9
"SL_RESULT_CONTENT_NOT_FOUND", // 10
"SL_RESULT_PERMISSION_DENIED", // 11
"SL_RESULT_FEATURE_UNSUPPORTED", // 12
"SL_RESULT_INTERNAL_ERROR", // 13
"SL_RESULT_UNKNOWN_ERROR", // 14
"SL_RESULT_OPERATION_ABORTED", // 15
"SL_RESULT_CONTROL_LOST", // 16
};
if (code >= arraysize(sl_error_strings)) {
return "SL_RESULT_UNKNOWN_ERROR";
}
return sl_error_strings[code];
}
SLDataFormat_PCM CreatePCMConfiguration(size_t channels,
int sample_rate,
size_t bits_per_sample) {
RTC_CHECK_EQ(bits_per_sample, SL_PCMSAMPLEFORMAT_FIXED_16);
SLDataFormat_PCM format;
format.formatType = SL_DATAFORMAT_PCM;
format.numChannels = static_cast<SLuint32>(channels);
// Note that, the unit of sample rate is actually in milliHertz and not Hertz.
switch (sample_rate) {
case 8000:
format.samplesPerSec = SL_SAMPLINGRATE_8;
break;
case 16000:
format.samplesPerSec = SL_SAMPLINGRATE_16;
break;
case 22050:
format.samplesPerSec = SL_SAMPLINGRATE_22_05;
break;
case 32000:
format.samplesPerSec = SL_SAMPLINGRATE_32;
break;
case 44100:
format.samplesPerSec = SL_SAMPLINGRATE_44_1;
break;
case 48000:
format.samplesPerSec = SL_SAMPLINGRATE_48;
break;
case 64000:
format.samplesPerSec = SL_SAMPLINGRATE_64;
break;
case 88200:
format.samplesPerSec = SL_SAMPLINGRATE_88_2;
break;
case 96000:
format.samplesPerSec = SL_SAMPLINGRATE_96;
break;
default:
RTC_CHECK(false) << "Unsupported sample rate: " << sample_rate;
break;
}
format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
format.endianness = SL_BYTEORDER_LITTLEENDIAN;
if (format.numChannels == 1) {
format.channelMask = SL_SPEAKER_FRONT_CENTER;
} else if (format.numChannels == 2) {
format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
} else {
RTC_CHECK(false) << "Unsupported number of channels: "
<< format.numChannels;
}
return format;
}
} // namespace webrtc

View file

@ -0,0 +1,62 @@
/*
* 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_OPENSLES_COMMON_H_
#define MODULES_AUDIO_DEVICE_ANDROID_OPENSLES_COMMON_H_
#include <SLES/OpenSLES.h>
#include <stddef.h>
#include "rtc_base/checks.h"
namespace webrtc {
// Returns a string representation given an integer SL_RESULT_XXX code.
// The mapping can be found in <SLES/OpenSLES.h>.
const char* GetSLErrorString(size_t code);
// Configures an SL_DATAFORMAT_PCM structure based on native audio parameters.
SLDataFormat_PCM CreatePCMConfiguration(size_t channels,
int sample_rate,
size_t bits_per_sample);
// Helper class for using SLObjectItf interfaces.
template <typename SLType, typename SLDerefType>
class ScopedSLObject {
public:
ScopedSLObject() : obj_(nullptr) {}
~ScopedSLObject() { Reset(); }
SLType* Receive() {
RTC_DCHECK(!obj_);
return &obj_;
}
SLDerefType operator->() { return *obj_; }
SLType Get() const { return obj_; }
void Reset() {
if (obj_) {
(*obj_)->Destroy(obj_);
obj_ = nullptr;
}
}
private:
SLType obj_;
};
typedef ScopedSLObject<SLObjectItf, const SLObjectItf_*> ScopedSLObjectItf;
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_OPENSLES_COMMON_H_

View file

@ -0,0 +1,434 @@
/*
* Copyright (c) 2015 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 "modules/audio_device/android/opensles_player.h"
#include <android/log.h>
#include <memory>
#include "api/array_view.h"
#include "modules/audio_device/android/audio_common.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/fine_audio_buffer.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/platform_thread.h"
#include "rtc_base/time_utils.h"
#define TAG "OpenSLESPlayer"
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define RETURN_ON_ERROR(op, ...) \
do { \
SLresult err = (op); \
if (err != SL_RESULT_SUCCESS) { \
ALOGE("%s failed: %s", #op, GetSLErrorString(err)); \
return __VA_ARGS__; \
} \
} while (0)
namespace webrtc {
OpenSLESPlayer::OpenSLESPlayer(AudioManager* audio_manager)
: audio_manager_(audio_manager),
audio_parameters_(audio_manager->GetPlayoutAudioParameters()),
audio_device_buffer_(nullptr),
initialized_(false),
playing_(false),
buffer_index_(0),
engine_(nullptr),
player_(nullptr),
simple_buffer_queue_(nullptr),
volume_(nullptr),
last_play_time_(0) {
ALOGD("ctor[tid=%d]", rtc::CurrentThreadId());
// Use native audio output parameters provided by the audio manager and
// define the PCM format structure.
pcm_format_ = CreatePCMConfiguration(audio_parameters_.channels(),
audio_parameters_.sample_rate(),
audio_parameters_.bits_per_sample());
// Detach from this thread since we want to use the checker to verify calls
// from the internal audio thread.
thread_checker_opensles_.Detach();
}
OpenSLESPlayer::~OpenSLESPlayer() {
ALOGD("dtor[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
Terminate();
DestroyAudioPlayer();
DestroyMix();
engine_ = nullptr;
RTC_DCHECK(!engine_);
RTC_DCHECK(!output_mix_.Get());
RTC_DCHECK(!player_);
RTC_DCHECK(!simple_buffer_queue_);
RTC_DCHECK(!volume_);
}
int OpenSLESPlayer::Init() {
ALOGD("Init[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
if (audio_parameters_.channels() == 2) {
ALOGW("Stereo mode is enabled");
}
return 0;
}
int OpenSLESPlayer::Terminate() {
ALOGD("Terminate[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
StopPlayout();
return 0;
}
int OpenSLESPlayer::InitPlayout() {
ALOGD("InitPlayout[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!initialized_);
RTC_DCHECK(!playing_);
if (!ObtainEngineInterface()) {
ALOGE("Failed to obtain SL Engine interface");
return -1;
}
CreateMix();
initialized_ = true;
buffer_index_ = 0;
return 0;
}
int OpenSLESPlayer::StartPlayout() {
ALOGD("StartPlayout[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(initialized_);
RTC_DCHECK(!playing_);
if (fine_audio_buffer_) {
fine_audio_buffer_->ResetPlayout();
}
// The number of lower latency audio players is limited, hence we create the
// audio player in Start() and destroy it in Stop().
CreateAudioPlayer();
// Fill up audio buffers to avoid initial glitch and to ensure that playback
// starts when mode is later changed to SL_PLAYSTATE_PLAYING.
// TODO(henrika): we can save some delay by only making one call to
// EnqueuePlayoutData. Most likely not worth the risk of adding a glitch.
last_play_time_ = rtc::Time();
for (int i = 0; i < kNumOfOpenSLESBuffers; ++i) {
EnqueuePlayoutData(true);
}
// Start streaming data by setting the play state to SL_PLAYSTATE_PLAYING.
// For a player object, when the object is in the SL_PLAYSTATE_PLAYING
// state, adding buffers will implicitly start playback.
RETURN_ON_ERROR((*player_)->SetPlayState(player_, SL_PLAYSTATE_PLAYING), -1);
playing_ = (GetPlayState() == SL_PLAYSTATE_PLAYING);
RTC_DCHECK(playing_);
return 0;
}
int OpenSLESPlayer::StopPlayout() {
ALOGD("StopPlayout[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
if (!initialized_ || !playing_) {
return 0;
}
// Stop playing by setting the play state to SL_PLAYSTATE_STOPPED.
RETURN_ON_ERROR((*player_)->SetPlayState(player_, SL_PLAYSTATE_STOPPED), -1);
// Clear the buffer queue to flush out any remaining data.
RETURN_ON_ERROR((*simple_buffer_queue_)->Clear(simple_buffer_queue_), -1);
#if RTC_DCHECK_IS_ON
// Verify that the buffer queue is in fact cleared as it should.
SLAndroidSimpleBufferQueueState buffer_queue_state;
(*simple_buffer_queue_)->GetState(simple_buffer_queue_, &buffer_queue_state);
RTC_DCHECK_EQ(0, buffer_queue_state.count);
RTC_DCHECK_EQ(0, buffer_queue_state.index);
#endif
// The number of lower latency audio players is limited, hence we create the
// audio player in Start() and destroy it in Stop().
DestroyAudioPlayer();
thread_checker_opensles_.Detach();
initialized_ = false;
playing_ = false;
return 0;
}
int OpenSLESPlayer::SpeakerVolumeIsAvailable(bool& available) {
available = false;
return 0;
}
int OpenSLESPlayer::MaxSpeakerVolume(uint32_t& maxVolume) const {
return -1;
}
int OpenSLESPlayer::MinSpeakerVolume(uint32_t& minVolume) const {
return -1;
}
int OpenSLESPlayer::SetSpeakerVolume(uint32_t volume) {
return -1;
}
int OpenSLESPlayer::SpeakerVolume(uint32_t& volume) const {
return -1;
}
void OpenSLESPlayer::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
ALOGD("AttachAudioBuffer");
RTC_DCHECK(thread_checker_.IsCurrent());
audio_device_buffer_ = audioBuffer;
const int sample_rate_hz = audio_parameters_.sample_rate();
ALOGD("SetPlayoutSampleRate(%d)", sample_rate_hz);
audio_device_buffer_->SetPlayoutSampleRate(sample_rate_hz);
const size_t channels = audio_parameters_.channels();
ALOGD("SetPlayoutChannels(%zu)", channels);
audio_device_buffer_->SetPlayoutChannels(channels);
RTC_CHECK(audio_device_buffer_);
AllocateDataBuffers();
}
void OpenSLESPlayer::AllocateDataBuffers() {
ALOGD("AllocateDataBuffers");
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!simple_buffer_queue_);
RTC_CHECK(audio_device_buffer_);
// Create a modified audio buffer class which allows us to ask for any number
// of samples (and not only multiple of 10ms) to match the native OpenSL ES
// buffer size. The native buffer size corresponds to the
// PROPERTY_OUTPUT_FRAMES_PER_BUFFER property which is the number of audio
// frames that the HAL (Hardware Abstraction Layer) buffer can hold. It is
// recommended to construct audio buffers so that they contain an exact
// multiple of this number. If so, callbacks will occur at regular intervals,
// which reduces jitter.
const size_t buffer_size_in_samples =
audio_parameters_.frames_per_buffer() * audio_parameters_.channels();
ALOGD("native buffer size: %zu", buffer_size_in_samples);
ALOGD("native buffer size in ms: %.2f",
audio_parameters_.GetBufferSizeInMilliseconds());
fine_audio_buffer_ = std::make_unique<FineAudioBuffer>(audio_device_buffer_);
// Allocated memory for audio buffers.
for (int i = 0; i < kNumOfOpenSLESBuffers; ++i) {
audio_buffers_[i].reset(new SLint16[buffer_size_in_samples]);
}
}
bool OpenSLESPlayer::ObtainEngineInterface() {
ALOGD("ObtainEngineInterface");
RTC_DCHECK(thread_checker_.IsCurrent());
if (engine_)
return true;
// Get access to (or create if not already existing) the global OpenSL Engine
// object.
SLObjectItf engine_object = audio_manager_->GetOpenSLEngine();
if (engine_object == nullptr) {
ALOGE("Failed to access the global OpenSL engine");
return false;
}
// Get the SL Engine Interface which is implicit.
RETURN_ON_ERROR(
(*engine_object)->GetInterface(engine_object, SL_IID_ENGINE, &engine_),
false);
return true;
}
bool OpenSLESPlayer::CreateMix() {
ALOGD("CreateMix");
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(engine_);
if (output_mix_.Get())
return true;
// Create the ouput mix on the engine object. No interfaces will be used.
RETURN_ON_ERROR((*engine_)->CreateOutputMix(engine_, output_mix_.Receive(), 0,
nullptr, nullptr),
false);
RETURN_ON_ERROR(output_mix_->Realize(output_mix_.Get(), SL_BOOLEAN_FALSE),
false);
return true;
}
void OpenSLESPlayer::DestroyMix() {
ALOGD("DestroyMix");
RTC_DCHECK(thread_checker_.IsCurrent());
if (!output_mix_.Get())
return;
output_mix_.Reset();
}
bool OpenSLESPlayer::CreateAudioPlayer() {
ALOGD("CreateAudioPlayer");
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(output_mix_.Get());
if (player_object_.Get())
return true;
RTC_DCHECK(!player_);
RTC_DCHECK(!simple_buffer_queue_);
RTC_DCHECK(!volume_);
// source: Android Simple Buffer Queue Data Locator is source.
SLDataLocator_AndroidSimpleBufferQueue simple_buffer_queue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
static_cast<SLuint32>(kNumOfOpenSLESBuffers)};
SLDataSource audio_source = {&simple_buffer_queue, &pcm_format_};
// sink: OutputMix-based data is sink.
SLDataLocator_OutputMix locator_output_mix = {SL_DATALOCATOR_OUTPUTMIX,
output_mix_.Get()};
SLDataSink audio_sink = {&locator_output_mix, nullptr};
// Define interfaces that we indend to use and realize.
const SLInterfaceID interface_ids[] = {SL_IID_ANDROIDCONFIGURATION,
SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
const SLboolean interface_required[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
SL_BOOLEAN_TRUE};
// Create the audio player on the engine interface.
RETURN_ON_ERROR(
(*engine_)->CreateAudioPlayer(
engine_, player_object_.Receive(), &audio_source, &audio_sink,
arraysize(interface_ids), interface_ids, interface_required),
false);
// Use the Android configuration interface to set platform-specific
// parameters. Should be done before player is realized.
SLAndroidConfigurationItf player_config;
RETURN_ON_ERROR(
player_object_->GetInterface(player_object_.Get(),
SL_IID_ANDROIDCONFIGURATION, &player_config),
false);
// Set audio player configuration to SL_ANDROID_STREAM_VOICE which
// corresponds to android.media.AudioManager.STREAM_VOICE_CALL.
SLint32 stream_type = SL_ANDROID_STREAM_VOICE;
RETURN_ON_ERROR(
(*player_config)
->SetConfiguration(player_config, SL_ANDROID_KEY_STREAM_TYPE,
&stream_type, sizeof(SLint32)),
false);
// Realize the audio player object after configuration has been set.
RETURN_ON_ERROR(
player_object_->Realize(player_object_.Get(), SL_BOOLEAN_FALSE), false);
// Get the SLPlayItf interface on the audio player.
RETURN_ON_ERROR(
player_object_->GetInterface(player_object_.Get(), SL_IID_PLAY, &player_),
false);
// Get the SLAndroidSimpleBufferQueueItf interface on the audio player.
RETURN_ON_ERROR(
player_object_->GetInterface(player_object_.Get(), SL_IID_BUFFERQUEUE,
&simple_buffer_queue_),
false);
// Register callback method for the Android Simple Buffer Queue interface.
// This method will be called when the native audio layer needs audio data.
RETURN_ON_ERROR((*simple_buffer_queue_)
->RegisterCallback(simple_buffer_queue_,
SimpleBufferQueueCallback, this),
false);
// Get the SLVolumeItf interface on the audio player.
RETURN_ON_ERROR(player_object_->GetInterface(player_object_.Get(),
SL_IID_VOLUME, &volume_),
false);
// TODO(henrika): might not be required to set volume to max here since it
// seems to be default on most devices. Might be required for unit tests.
// RETURN_ON_ERROR((*volume_)->SetVolumeLevel(volume_, 0), false);
return true;
}
void OpenSLESPlayer::DestroyAudioPlayer() {
ALOGD("DestroyAudioPlayer");
RTC_DCHECK(thread_checker_.IsCurrent());
if (!player_object_.Get())
return;
(*simple_buffer_queue_)
->RegisterCallback(simple_buffer_queue_, nullptr, nullptr);
player_object_.Reset();
player_ = nullptr;
simple_buffer_queue_ = nullptr;
volume_ = nullptr;
}
// static
void OpenSLESPlayer::SimpleBufferQueueCallback(
SLAndroidSimpleBufferQueueItf caller,
void* context) {
OpenSLESPlayer* stream = reinterpret_cast<OpenSLESPlayer*>(context);
stream->FillBufferQueue();
}
void OpenSLESPlayer::FillBufferQueue() {
RTC_DCHECK(thread_checker_opensles_.IsCurrent());
SLuint32 state = GetPlayState();
if (state != SL_PLAYSTATE_PLAYING) {
ALOGW("Buffer callback in non-playing state!");
return;
}
EnqueuePlayoutData(false);
}
void OpenSLESPlayer::EnqueuePlayoutData(bool silence) {
// Check delta time between two successive callbacks and provide a warning
// if it becomes very large.
// TODO(henrika): using 150ms as upper limit but this value is rather random.
const uint32_t current_time = rtc::Time();
const uint32_t diff = current_time - last_play_time_;
if (diff > 150) {
ALOGW("Bad OpenSL ES playout timing, dT=%u [ms]", diff);
}
last_play_time_ = current_time;
SLint8* audio_ptr8 =
reinterpret_cast<SLint8*>(audio_buffers_[buffer_index_].get());
if (silence) {
RTC_DCHECK(thread_checker_.IsCurrent());
// Avoid acquiring real audio data from WebRTC and fill the buffer with
// zeros instead. Used to prime the buffer with silence and to avoid asking
// for audio data from two different threads.
memset(audio_ptr8, 0, audio_parameters_.GetBytesPerBuffer());
} else {
RTC_DCHECK(thread_checker_opensles_.IsCurrent());
// Read audio data from the WebRTC source using the FineAudioBuffer object
// to adjust for differences in buffer size between WebRTC (10ms) and native
// OpenSL ES. Use hardcoded delay estimate since OpenSL ES does not support
// delay estimation.
fine_audio_buffer_->GetPlayoutData(
rtc::ArrayView<int16_t>(audio_buffers_[buffer_index_].get(),
audio_parameters_.frames_per_buffer() *
audio_parameters_.channels()),
25);
}
// Enqueue the decoded audio buffer for playback.
SLresult err = (*simple_buffer_queue_)
->Enqueue(simple_buffer_queue_, audio_ptr8,
audio_parameters_.GetBytesPerBuffer());
if (SL_RESULT_SUCCESS != err) {
ALOGE("Enqueue failed: %d", err);
}
buffer_index_ = (buffer_index_ + 1) % kNumOfOpenSLESBuffers;
}
SLuint32 OpenSLESPlayer::GetPlayState() const {
RTC_DCHECK(player_);
SLuint32 state;
SLresult err = (*player_)->GetPlayState(player_, &state);
if (SL_RESULT_SUCCESS != err) {
ALOGE("GetPlayState failed: %d", err);
}
return state;
}
} // namespace webrtc

View file

@ -0,0 +1,195 @@
/*
* Copyright (c) 2015 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_OPENSLES_PLAYER_H_
#define MODULES_AUDIO_DEVICE_ANDROID_OPENSLES_PLAYER_H_
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <SLES/OpenSLES_AndroidConfiguration.h>
#include "api/sequence_checker.h"
#include "modules/audio_device/android/audio_common.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/android/opensles_common.h"
#include "modules/audio_device/audio_device_generic.h"
#include "modules/audio_device/include/audio_device_defines.h"
#include "modules/utility/include/helpers_android.h"
namespace webrtc {
class FineAudioBuffer;
// Implements 16-bit mono PCM audio output support for Android using the
// C based OpenSL ES API. No calls from C/C++ to Java using JNI is done.
//
// An instance must be created and destroyed on one and the same thread.
// All public methods must also be called on the same thread. A thread checker
// will RTC_DCHECK if any method is called on an invalid thread. Decoded audio
// buffers are requested on a dedicated internal thread managed by the OpenSL
// ES layer.
//
// The existing design forces the user to call InitPlayout() after Stoplayout()
// to be able to call StartPlayout() again. This is inline with how the Java-
// based implementation works.
//
// OpenSL ES is a native C API which have no Dalvik-related overhead such as
// garbage collection pauses and it supports reduced audio output latency.
// If the device doesn't claim this feature but supports API level 9 (Android
// platform version 2.3) or later, then we can still use the OpenSL ES APIs but
// the output latency may be higher.
class OpenSLESPlayer {
public:
// Beginning with API level 17 (Android 4.2), a buffer count of 2 or more is
// required for lower latency. Beginning with API level 18 (Android 4.3), a
// buffer count of 1 is sufficient for lower latency. In addition, the buffer
// size and sample rate must be compatible with the device's native output
// configuration provided via the audio manager at construction.
// TODO(henrika): perhaps set this value dynamically based on OS version.
static const int kNumOfOpenSLESBuffers = 2;
explicit OpenSLESPlayer(AudioManager* audio_manager);
~OpenSLESPlayer();
int Init();
int Terminate();
int InitPlayout();
bool PlayoutIsInitialized() const { return initialized_; }
int StartPlayout();
int StopPlayout();
bool Playing() const { return playing_; }
int SpeakerVolumeIsAvailable(bool& available);
int SetSpeakerVolume(uint32_t volume);
int SpeakerVolume(uint32_t& volume) const;
int MaxSpeakerVolume(uint32_t& maxVolume) const;
int MinSpeakerVolume(uint32_t& minVolume) const;
void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer);
private:
// These callback methods are called when data is required for playout.
// They are both called from an internal "OpenSL ES thread" which is not
// attached to the Dalvik VM.
static void SimpleBufferQueueCallback(SLAndroidSimpleBufferQueueItf caller,
void* context);
void FillBufferQueue();
// Reads audio data in PCM format using the AudioDeviceBuffer.
// Can be called both on the main thread (during Start()) and from the
// internal audio thread while output streaming is active.
// If the `silence` flag is set, the audio is filled with zeros instead of
// asking the WebRTC layer for real audio data. This procedure is also known
// as audio priming.
void EnqueuePlayoutData(bool silence);
// Allocate memory for audio buffers which will be used to render audio
// via the SLAndroidSimpleBufferQueueItf interface.
void AllocateDataBuffers();
// Obtaines the SL Engine Interface from the existing global Engine object.
// The interface exposes creation methods of all the OpenSL ES object types.
// This method defines the `engine_` member variable.
bool ObtainEngineInterface();
// Creates/destroys the output mix object.
bool CreateMix();
void DestroyMix();
// Creates/destroys the audio player and the simple-buffer object.
// Also creates the volume object.
bool CreateAudioPlayer();
void DestroyAudioPlayer();
SLuint32 GetPlayState() const;
// Ensures that methods are called from the same thread as this object is
// created on.
SequenceChecker thread_checker_;
// Stores thread ID in first call to SimpleBufferQueueCallback() from internal
// non-application thread which is not attached to the Dalvik JVM.
// Detached during construction of this object.
SequenceChecker thread_checker_opensles_;
// Raw pointer to the audio manager injected at construction. Used to cache
// audio parameters and to access the global SL engine object needed by the
// ObtainEngineInterface() method. The audio manager outlives any instance of
// this class.
AudioManager* audio_manager_;
// Contains audio parameters provided to this class at construction by the
// AudioManager.
const AudioParameters audio_parameters_;
// Raw pointer handle provided to us in AttachAudioBuffer(). Owned by the
// AudioDeviceModuleImpl class and called by AudioDeviceModule::Create().
AudioDeviceBuffer* audio_device_buffer_;
bool initialized_;
bool playing_;
// PCM-type format definition.
// TODO(henrika): add support for SLAndroidDataFormat_PCM_EX (android-21) if
// 32-bit float representation is needed.
SLDataFormat_PCM pcm_format_;
// Queue of audio buffers to be used by the player object for rendering
// audio.
std::unique_ptr<SLint16[]> audio_buffers_[kNumOfOpenSLESBuffers];
// FineAudioBuffer takes an AudioDeviceBuffer which delivers audio data
// in chunks of 10ms. It then allows for this data to be pulled in
// a finer or coarser granularity. I.e. interacting with this class instead
// of directly with the AudioDeviceBuffer one can ask for any number of
// audio data samples.
// Example: native buffer size can be 192 audio frames at 48kHz sample rate.
// WebRTC will provide 480 audio frames per 10ms but OpenSL ES asks for 192
// in each callback (one every 4th ms). This class can then ask for 192 and
// the FineAudioBuffer will ask WebRTC for new data approximately only every
// second callback and also cache non-utilized audio.
std::unique_ptr<FineAudioBuffer> fine_audio_buffer_;
// Keeps track of active audio buffer 'n' in the audio_buffers_[n] queue.
// Example (kNumOfOpenSLESBuffers = 2): counts 0, 1, 0, 1, ...
int buffer_index_;
// This interface exposes creation methods for all the OpenSL ES object types.
// It is the OpenSL ES API entry point.
SLEngineItf engine_;
// Output mix object to be used by the player object.
webrtc::ScopedSLObjectItf output_mix_;
// The audio player media object plays out audio to the speakers. It also
// supports volume control.
webrtc::ScopedSLObjectItf player_object_;
// This interface is supported on the audio player and it controls the state
// of the audio player.
SLPlayItf player_;
// The Android Simple Buffer Queue interface is supported on the audio player
// and it provides methods to send audio data from the source to the audio
// player for rendering.
SLAndroidSimpleBufferQueueItf simple_buffer_queue_;
// This interface exposes controls for manipulating the objects audio volume
// properties. This interface is supported on the Audio Player object.
SLVolumeItf volume_;
// Last time the OpenSL ES layer asked for audio data to play out.
uint32_t last_play_time_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_OPENSLES_PLAYER_H_

View file

@ -0,0 +1,431 @@
/*
* Copyright (c) 2016 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 "modules/audio_device/android/opensles_recorder.h"
#include <android/log.h>
#include <memory>
#include "api/array_view.h"
#include "modules/audio_device/android/audio_common.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/fine_audio_buffer.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/platform_thread.h"
#include "rtc_base/time_utils.h"
#define TAG "OpenSLESRecorder"
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOG_ON_ERROR(op) \
[](SLresult err) { \
if (err != SL_RESULT_SUCCESS) { \
ALOGE("%s:%d %s failed: %s", __FILE__, __LINE__, #op, \
GetSLErrorString(err)); \
return true; \
} \
return false; \
}(op)
namespace webrtc {
OpenSLESRecorder::OpenSLESRecorder(AudioManager* audio_manager)
: audio_manager_(audio_manager),
audio_parameters_(audio_manager->GetRecordAudioParameters()),
audio_device_buffer_(nullptr),
initialized_(false),
recording_(false),
engine_(nullptr),
recorder_(nullptr),
simple_buffer_queue_(nullptr),
buffer_index_(0),
last_rec_time_(0) {
ALOGD("ctor[tid=%d]", rtc::CurrentThreadId());
// Detach from this thread since we want to use the checker to verify calls
// from the internal audio thread.
thread_checker_opensles_.Detach();
// Use native audio output parameters provided by the audio manager and
// define the PCM format structure.
pcm_format_ = CreatePCMConfiguration(audio_parameters_.channels(),
audio_parameters_.sample_rate(),
audio_parameters_.bits_per_sample());
}
OpenSLESRecorder::~OpenSLESRecorder() {
ALOGD("dtor[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
Terminate();
DestroyAudioRecorder();
engine_ = nullptr;
RTC_DCHECK(!engine_);
RTC_DCHECK(!recorder_);
RTC_DCHECK(!simple_buffer_queue_);
}
int OpenSLESRecorder::Init() {
ALOGD("Init[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
if (audio_parameters_.channels() == 2) {
ALOGD("Stereo mode is enabled");
}
return 0;
}
int OpenSLESRecorder::Terminate() {
ALOGD("Terminate[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
StopRecording();
return 0;
}
int OpenSLESRecorder::InitRecording() {
ALOGD("InitRecording[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!initialized_);
RTC_DCHECK(!recording_);
if (!ObtainEngineInterface()) {
ALOGE("Failed to obtain SL Engine interface");
return -1;
}
CreateAudioRecorder();
initialized_ = true;
buffer_index_ = 0;
return 0;
}
int OpenSLESRecorder::StartRecording() {
ALOGD("StartRecording[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(initialized_);
RTC_DCHECK(!recording_);
if (fine_audio_buffer_) {
fine_audio_buffer_->ResetRecord();
}
// Add buffers to the queue before changing state to SL_RECORDSTATE_RECORDING
// to ensure that recording starts as soon as the state is modified. On some
// devices, SLAndroidSimpleBufferQueue::Clear() used in Stop() does not flush
// the buffers as intended and we therefore check the number of buffers
// already queued first. Enqueue() can return SL_RESULT_BUFFER_INSUFFICIENT
// otherwise.
int num_buffers_in_queue = GetBufferCount();
for (int i = 0; i < kNumOfOpenSLESBuffers - num_buffers_in_queue; ++i) {
if (!EnqueueAudioBuffer()) {
recording_ = false;
return -1;
}
}
num_buffers_in_queue = GetBufferCount();
RTC_DCHECK_EQ(num_buffers_in_queue, kNumOfOpenSLESBuffers);
LogBufferState();
// Start audio recording by changing the state to SL_RECORDSTATE_RECORDING.
// Given that buffers are already enqueued, recording should start at once.
// The macro returns -1 if recording fails to start.
last_rec_time_ = rtc::Time();
if (LOG_ON_ERROR(
(*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_RECORDING))) {
return -1;
}
recording_ = (GetRecordState() == SL_RECORDSTATE_RECORDING);
RTC_DCHECK(recording_);
return 0;
}
int OpenSLESRecorder::StopRecording() {
ALOGD("StopRecording[tid=%d]", rtc::CurrentThreadId());
RTC_DCHECK(thread_checker_.IsCurrent());
if (!initialized_ || !recording_) {
return 0;
}
// Stop recording by setting the record state to SL_RECORDSTATE_STOPPED.
if (LOG_ON_ERROR(
(*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_STOPPED))) {
return -1;
}
// Clear the buffer queue to get rid of old data when resuming recording.
if (LOG_ON_ERROR((*simple_buffer_queue_)->Clear(simple_buffer_queue_))) {
return -1;
}
thread_checker_opensles_.Detach();
initialized_ = false;
recording_ = false;
return 0;
}
void OpenSLESRecorder::AttachAudioBuffer(AudioDeviceBuffer* audio_buffer) {
ALOGD("AttachAudioBuffer");
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_CHECK(audio_buffer);
audio_device_buffer_ = audio_buffer;
// Ensure that the audio device buffer is informed about the native sample
// rate used on the recording side.
const int sample_rate_hz = audio_parameters_.sample_rate();
ALOGD("SetRecordingSampleRate(%d)", sample_rate_hz);
audio_device_buffer_->SetRecordingSampleRate(sample_rate_hz);
// Ensure that the audio device buffer is informed about the number of
// channels preferred by the OS on the recording side.
const size_t channels = audio_parameters_.channels();
ALOGD("SetRecordingChannels(%zu)", channels);
audio_device_buffer_->SetRecordingChannels(channels);
// Allocated memory for internal data buffers given existing audio parameters.
AllocateDataBuffers();
}
int OpenSLESRecorder::EnableBuiltInAEC(bool enable) {
ALOGD("EnableBuiltInAEC(%d)", enable);
RTC_DCHECK(thread_checker_.IsCurrent());
ALOGE("Not implemented");
return 0;
}
int OpenSLESRecorder::EnableBuiltInAGC(bool enable) {
ALOGD("EnableBuiltInAGC(%d)", enable);
RTC_DCHECK(thread_checker_.IsCurrent());
ALOGE("Not implemented");
return 0;
}
int OpenSLESRecorder::EnableBuiltInNS(bool enable) {
ALOGD("EnableBuiltInNS(%d)", enable);
RTC_DCHECK(thread_checker_.IsCurrent());
ALOGE("Not implemented");
return 0;
}
bool OpenSLESRecorder::ObtainEngineInterface() {
ALOGD("ObtainEngineInterface");
RTC_DCHECK(thread_checker_.IsCurrent());
if (engine_)
return true;
// Get access to (or create if not already existing) the global OpenSL Engine
// object.
SLObjectItf engine_object = audio_manager_->GetOpenSLEngine();
if (engine_object == nullptr) {
ALOGE("Failed to access the global OpenSL engine");
return false;
}
// Get the SL Engine Interface which is implicit.
if (LOG_ON_ERROR(
(*engine_object)
->GetInterface(engine_object, SL_IID_ENGINE, &engine_))) {
return false;
}
return true;
}
bool OpenSLESRecorder::CreateAudioRecorder() {
ALOGD("CreateAudioRecorder");
RTC_DCHECK(thread_checker_.IsCurrent());
if (recorder_object_.Get())
return true;
RTC_DCHECK(!recorder_);
RTC_DCHECK(!simple_buffer_queue_);
// Audio source configuration.
SLDataLocator_IODevice mic_locator = {SL_DATALOCATOR_IODEVICE,
SL_IODEVICE_AUDIOINPUT,
SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
SLDataSource audio_source = {&mic_locator, NULL};
// Audio sink configuration.
SLDataLocator_AndroidSimpleBufferQueue buffer_queue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
static_cast<SLuint32>(kNumOfOpenSLESBuffers)};
SLDataSink audio_sink = {&buffer_queue, &pcm_format_};
// Create the audio recorder object (requires the RECORD_AUDIO permission).
// Do not realize the recorder yet. Set the configuration first.
const SLInterfaceID interface_id[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
SL_IID_ANDROIDCONFIGURATION};
const SLboolean interface_required[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
if (LOG_ON_ERROR((*engine_)->CreateAudioRecorder(
engine_, recorder_object_.Receive(), &audio_source, &audio_sink,
arraysize(interface_id), interface_id, interface_required))) {
return false;
}
// Configure the audio recorder (before it is realized).
SLAndroidConfigurationItf recorder_config;
if (LOG_ON_ERROR((recorder_object_->GetInterface(recorder_object_.Get(),
SL_IID_ANDROIDCONFIGURATION,
&recorder_config)))) {
return false;
}
// Uses the default microphone tuned for audio communication.
// Note that, SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION leads to a fast
// track but also excludes usage of required effects like AEC, AGC and NS.
// SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION
SLint32 stream_type = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION;
if (LOG_ON_ERROR(((*recorder_config)
->SetConfiguration(recorder_config,
SL_ANDROID_KEY_RECORDING_PRESET,
&stream_type, sizeof(SLint32))))) {
return false;
}
// The audio recorder can now be realized (in synchronous mode).
if (LOG_ON_ERROR((recorder_object_->Realize(recorder_object_.Get(),
SL_BOOLEAN_FALSE)))) {
return false;
}
// Get the implicit recorder interface (SL_IID_RECORD).
if (LOG_ON_ERROR((recorder_object_->GetInterface(
recorder_object_.Get(), SL_IID_RECORD, &recorder_)))) {
return false;
}
// Get the simple buffer queue interface (SL_IID_ANDROIDSIMPLEBUFFERQUEUE).
// It was explicitly requested.
if (LOG_ON_ERROR((recorder_object_->GetInterface(
recorder_object_.Get(), SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&simple_buffer_queue_)))) {
return false;
}
// Register the input callback for the simple buffer queue.
// This callback will be called when receiving new data from the device.
if (LOG_ON_ERROR(((*simple_buffer_queue_)
->RegisterCallback(simple_buffer_queue_,
SimpleBufferQueueCallback, this)))) {
return false;
}
return true;
}
void OpenSLESRecorder::DestroyAudioRecorder() {
ALOGD("DestroyAudioRecorder");
RTC_DCHECK(thread_checker_.IsCurrent());
if (!recorder_object_.Get())
return;
(*simple_buffer_queue_)
->RegisterCallback(simple_buffer_queue_, nullptr, nullptr);
recorder_object_.Reset();
recorder_ = nullptr;
simple_buffer_queue_ = nullptr;
}
void OpenSLESRecorder::SimpleBufferQueueCallback(
SLAndroidSimpleBufferQueueItf buffer_queue,
void* context) {
OpenSLESRecorder* stream = static_cast<OpenSLESRecorder*>(context);
stream->ReadBufferQueue();
}
void OpenSLESRecorder::AllocateDataBuffers() {
ALOGD("AllocateDataBuffers");
RTC_DCHECK(thread_checker_.IsCurrent());
RTC_DCHECK(!simple_buffer_queue_);
RTC_CHECK(audio_device_buffer_);
// Create a modified audio buffer class which allows us to deliver any number
// of samples (and not only multiple of 10ms) to match the native audio unit
// buffer size.
ALOGD("frames per native buffer: %zu", audio_parameters_.frames_per_buffer());
ALOGD("frames per 10ms buffer: %zu",
audio_parameters_.frames_per_10ms_buffer());
ALOGD("bytes per native buffer: %zu", audio_parameters_.GetBytesPerBuffer());
ALOGD("native sample rate: %d", audio_parameters_.sample_rate());
RTC_DCHECK(audio_device_buffer_);
fine_audio_buffer_ = std::make_unique<FineAudioBuffer>(audio_device_buffer_);
// Allocate queue of audio buffers that stores recorded audio samples.
const int buffer_size_samples =
audio_parameters_.frames_per_buffer() * audio_parameters_.channels();
audio_buffers_.reset(new std::unique_ptr<SLint16[]>[kNumOfOpenSLESBuffers]);
for (int i = 0; i < kNumOfOpenSLESBuffers; ++i) {
audio_buffers_[i].reset(new SLint16[buffer_size_samples]);
}
}
void OpenSLESRecorder::ReadBufferQueue() {
RTC_DCHECK(thread_checker_opensles_.IsCurrent());
SLuint32 state = GetRecordState();
if (state != SL_RECORDSTATE_RECORDING) {
ALOGW("Buffer callback in non-recording state!");
return;
}
// Check delta time between two successive callbacks and provide a warning
// if it becomes very large.
// TODO(henrika): using 150ms as upper limit but this value is rather random.
const uint32_t current_time = rtc::Time();
const uint32_t diff = current_time - last_rec_time_;
if (diff > 150) {
ALOGW("Bad OpenSL ES record timing, dT=%u [ms]", diff);
}
last_rec_time_ = current_time;
// Send recorded audio data to the WebRTC sink.
// TODO(henrika): fix delay estimates. It is OK to use fixed values for now
// since there is no support to turn off built-in EC in combination with
// OpenSL ES anyhow. Hence, as is, the WebRTC based AEC (which would use
// these estimates) will never be active.
fine_audio_buffer_->DeliverRecordedData(
rtc::ArrayView<const int16_t>(
audio_buffers_[buffer_index_].get(),
audio_parameters_.frames_per_buffer() * audio_parameters_.channels()),
25);
// Enqueue the utilized audio buffer and use if for recording again.
EnqueueAudioBuffer();
}
bool OpenSLESRecorder::EnqueueAudioBuffer() {
SLresult err =
(*simple_buffer_queue_)
->Enqueue(
simple_buffer_queue_,
reinterpret_cast<SLint8*>(audio_buffers_[buffer_index_].get()),
audio_parameters_.GetBytesPerBuffer());
if (SL_RESULT_SUCCESS != err) {
ALOGE("Enqueue failed: %s", GetSLErrorString(err));
return false;
}
buffer_index_ = (buffer_index_ + 1) % kNumOfOpenSLESBuffers;
return true;
}
SLuint32 OpenSLESRecorder::GetRecordState() const {
RTC_DCHECK(recorder_);
SLuint32 state;
SLresult err = (*recorder_)->GetRecordState(recorder_, &state);
if (SL_RESULT_SUCCESS != err) {
ALOGE("GetRecordState failed: %s", GetSLErrorString(err));
}
return state;
}
SLAndroidSimpleBufferQueueState OpenSLESRecorder::GetBufferQueueState() const {
RTC_DCHECK(simple_buffer_queue_);
// state.count: Number of buffers currently in the queue.
// state.index: Index of the currently filling buffer. This is a linear index
// that keeps a cumulative count of the number of buffers recorded.
SLAndroidSimpleBufferQueueState state;
SLresult err =
(*simple_buffer_queue_)->GetState(simple_buffer_queue_, &state);
if (SL_RESULT_SUCCESS != err) {
ALOGE("GetState failed: %s", GetSLErrorString(err));
}
return state;
}
void OpenSLESRecorder::LogBufferState() const {
SLAndroidSimpleBufferQueueState state = GetBufferQueueState();
ALOGD("state.count:%d state.index:%d", state.count, state.index);
}
SLuint32 OpenSLESRecorder::GetBufferCount() {
SLAndroidSimpleBufferQueueState state = GetBufferQueueState();
return state.count;
}
} // namespace webrtc

View file

@ -0,0 +1,193 @@
/*
* Copyright (c) 2016 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.
*/
#ifndef MODULES_AUDIO_DEVICE_ANDROID_OPENSLES_RECORDER_H_
#define MODULES_AUDIO_DEVICE_ANDROID_OPENSLES_RECORDER_H_
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <SLES/OpenSLES_AndroidConfiguration.h>
#include <memory>
#include "api/sequence_checker.h"
#include "modules/audio_device/android/audio_common.h"
#include "modules/audio_device/android/audio_manager.h"
#include "modules/audio_device/android/opensles_common.h"
#include "modules/audio_device/audio_device_generic.h"
#include "modules/audio_device/include/audio_device_defines.h"
#include "modules/utility/include/helpers_android.h"
namespace webrtc {
class FineAudioBuffer;
// Implements 16-bit mono PCM audio input support for Android using the
// C based OpenSL ES API. No calls from C/C++ to Java using JNI is done.
//
// An instance must be created and destroyed on one and the same thread.
// All public methods must also be called on the same thread. A thread checker
// will RTC_DCHECK if any method is called on an invalid thread. Recorded audio
// buffers are provided on a dedicated internal thread managed by the OpenSL
// ES layer.
//
// The existing design forces the user to call InitRecording() after
// StopRecording() to be able to call StartRecording() again. This is inline
// with how the Java-based implementation works.
//
// As of API level 21, lower latency audio input is supported on select devices.
// To take advantage of this feature, first confirm that lower latency output is
// available. The capability for lower latency output is a prerequisite for the
// lower latency input feature. Then, create an AudioRecorder with the same
// sample rate and buffer size as would be used for output. OpenSL ES interfaces
// for input effects preclude the lower latency path.
// See https://developer.android.com/ndk/guides/audio/opensl-prog-notes.html
// for more details.
class OpenSLESRecorder {
public:
// Beginning with API level 17 (Android 4.2), a buffer count of 2 or more is
// required for lower latency. Beginning with API level 18 (Android 4.3), a
// buffer count of 1 is sufficient for lower latency. In addition, the buffer
// size and sample rate must be compatible with the device's native input
// configuration provided via the audio manager at construction.
// TODO(henrika): perhaps set this value dynamically based on OS version.
static const int kNumOfOpenSLESBuffers = 2;
explicit OpenSLESRecorder(AudioManager* audio_manager);
~OpenSLESRecorder();
int Init();
int Terminate();
int InitRecording();
bool RecordingIsInitialized() const { return initialized_; }
int StartRecording();
int StopRecording();
bool Recording() const { return recording_; }
void AttachAudioBuffer(AudioDeviceBuffer* audio_buffer);
// TODO(henrika): add support using OpenSL ES APIs when available.
int EnableBuiltInAEC(bool enable);
int EnableBuiltInAGC(bool enable);
int EnableBuiltInNS(bool enable);
private:
// Obtaines the SL Engine Interface from the existing global Engine object.
// The interface exposes creation methods of all the OpenSL ES object types.
// This method defines the `engine_` member variable.
bool ObtainEngineInterface();
// Creates/destroys the audio recorder and the simple-buffer queue object.
bool CreateAudioRecorder();
void DestroyAudioRecorder();
// Allocate memory for audio buffers which will be used to capture audio
// via the SLAndroidSimpleBufferQueueItf interface.
void AllocateDataBuffers();
// These callback methods are called when data has been written to the input
// buffer queue. They are both called from an internal "OpenSL ES thread"
// which is not attached to the Dalvik VM.
static void SimpleBufferQueueCallback(SLAndroidSimpleBufferQueueItf caller,
void* context);
void ReadBufferQueue();
// Wraps calls to SLAndroidSimpleBufferQueueState::Enqueue() and it can be
// called both on the main thread (but before recording has started) and from
// the internal audio thread while input streaming is active. It uses
// `simple_buffer_queue_` but no lock is needed since the initial calls from
// the main thread and the native callback thread are mutually exclusive.
bool EnqueueAudioBuffer();
// Returns the current recorder state.
SLuint32 GetRecordState() const;
// Returns the current buffer queue state.
SLAndroidSimpleBufferQueueState GetBufferQueueState() const;
// Number of buffers currently in the queue.
SLuint32 GetBufferCount();
// Prints a log message of the current queue state. Can be used for debugging
// purposes.
void LogBufferState() const;
// Ensures that methods are called from the same thread as this object is
// created on.
SequenceChecker thread_checker_;
// Stores thread ID in first call to SimpleBufferQueueCallback() from internal
// non-application thread which is not attached to the Dalvik JVM.
// Detached during construction of this object.
SequenceChecker thread_checker_opensles_;
// Raw pointer to the audio manager injected at construction. Used to cache
// audio parameters and to access the global SL engine object needed by the
// ObtainEngineInterface() method. The audio manager outlives any instance of
// this class.
AudioManager* const audio_manager_;
// Contains audio parameters provided to this class at construction by the
// AudioManager.
const AudioParameters audio_parameters_;
// Raw pointer handle provided to us in AttachAudioBuffer(). Owned by the
// AudioDeviceModuleImpl class and called by AudioDeviceModule::Create().
AudioDeviceBuffer* audio_device_buffer_;
// PCM-type format definition.
// TODO(henrika): add support for SLAndroidDataFormat_PCM_EX (android-21) if
// 32-bit float representation is needed.
SLDataFormat_PCM pcm_format_;
bool initialized_;
bool recording_;
// This interface exposes creation methods for all the OpenSL ES object types.
// It is the OpenSL ES API entry point.
SLEngineItf engine_;
// The audio recorder media object records audio to the destination specified
// by the data sink capturing it from the input specified by the data source.
webrtc::ScopedSLObjectItf recorder_object_;
// This interface is supported on the audio recorder object and it controls
// the state of the audio recorder.
SLRecordItf recorder_;
// The Android Simple Buffer Queue interface is supported on the audio
// recorder. For recording, an app should enqueue empty buffers. When a
// registered callback sends notification that the system has finished writing
// data to the buffer, the app can read the buffer.
SLAndroidSimpleBufferQueueItf simple_buffer_queue_;
// Consumes audio of native buffer size and feeds the WebRTC layer with 10ms
// chunks of audio.
std::unique_ptr<FineAudioBuffer> fine_audio_buffer_;
// Queue of audio buffers to be used by the recorder object for capturing
// audio. They will be used in a Round-robin way and the size of each buffer
// is given by AudioParameters::frames_per_buffer(), i.e., it corresponds to
// the native OpenSL ES buffer size.
std::unique_ptr<std::unique_ptr<SLint16[]>[]> audio_buffers_;
// Keeps track of active audio buffer 'n' in the audio_buffers_[n] queue.
// Example (kNumOfOpenSLESBuffers = 2): counts 0, 1, 0, 1, ...
int buffer_index_;
// Last time the OpenSL ES layer delivered recorded audio data.
uint32_t last_rec_time_;
};
} // namespace webrtc
#endif // MODULES_AUDIO_DEVICE_ANDROID_OPENSLES_RECORDER_H_