xref: /aosp_15_r20/external/webrtc/sdk/android/src/jni/audio_device/audio_record_jni.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "sdk/android/src/jni/audio_device/audio_record_jni.h"
12 
13 #include <string>
14 #include <utility>
15 
16 #include "rtc_base/arraysize.h"
17 #include "rtc_base/checks.h"
18 #include "rtc_base/logging.h"
19 #include "rtc_base/platform_thread.h"
20 #include "rtc_base/time_utils.h"
21 #include "sdk/android/generated_java_audio_device_module_native_jni/WebRtcAudioRecord_jni.h"
22 #include "sdk/android/src/jni/audio_device/audio_common.h"
23 #include "sdk/android/src/jni/jni_helpers.h"
24 #include "system_wrappers/include/metrics.h"
25 
26 namespace webrtc {
27 
28 namespace jni {
29 
30 namespace {
31 // Scoped class which logs its time of life as a UMA statistic. It generates
32 // a histogram which measures the time it takes for a method/scope to execute.
33 class ScopedHistogramTimer {
34  public:
ScopedHistogramTimer(const std::string & name)35   explicit ScopedHistogramTimer(const std::string& name)
36       : histogram_name_(name), start_time_ms_(rtc::TimeMillis()) {}
~ScopedHistogramTimer()37   ~ScopedHistogramTimer() {
38     const int64_t life_time_ms = rtc::TimeSince(start_time_ms_);
39     RTC_HISTOGRAM_COUNTS_1000(histogram_name_, life_time_ms);
40     RTC_LOG(LS_INFO) << histogram_name_ << ": " << life_time_ms;
41   }
42 
43  private:
44   const std::string histogram_name_;
45   int64_t start_time_ms_;
46 };
47 
48 }  // namespace
49 
CreateJavaWebRtcAudioRecord(JNIEnv * env,const JavaRef<jobject> & j_context,const JavaRef<jobject> & j_audio_manager)50 ScopedJavaLocalRef<jobject> AudioRecordJni::CreateJavaWebRtcAudioRecord(
51     JNIEnv* env,
52     const JavaRef<jobject>& j_context,
53     const JavaRef<jobject>& j_audio_manager) {
54   return Java_WebRtcAudioRecord_Constructor(env, j_context, j_audio_manager);
55 }
56 
AudioRecordJni(JNIEnv * env,const AudioParameters & audio_parameters,int total_delay_ms,const JavaRef<jobject> & j_audio_record)57 AudioRecordJni::AudioRecordJni(JNIEnv* env,
58                                const AudioParameters& audio_parameters,
59                                int total_delay_ms,
60                                const JavaRef<jobject>& j_audio_record)
61     : j_audio_record_(env, j_audio_record),
62       audio_parameters_(audio_parameters),
63       total_delay_ms_(total_delay_ms),
64       direct_buffer_address_(nullptr),
65       direct_buffer_capacity_in_bytes_(0),
66       frames_per_buffer_(0),
67       initialized_(false),
68       recording_(false),
69       audio_device_buffer_(nullptr) {
70   RTC_LOG(LS_INFO) << "ctor";
71   RTC_DCHECK(audio_parameters_.is_valid());
72   Java_WebRtcAudioRecord_setNativeAudioRecord(env, j_audio_record_,
73                                               jni::jlongFromPointer(this));
74   // Detach from this thread since construction is allowed to happen on a
75   // different thread.
76   thread_checker_.Detach();
77   thread_checker_java_.Detach();
78 }
79 
~AudioRecordJni()80 AudioRecordJni::~AudioRecordJni() {
81   RTC_LOG(LS_INFO) << "dtor";
82   RTC_DCHECK(thread_checker_.IsCurrent());
83   Terminate();
84 }
85 
Init()86 int32_t AudioRecordJni::Init() {
87   RTC_LOG(LS_INFO) << "Init";
88   env_ = AttachCurrentThreadIfNeeded();
89   RTC_DCHECK(thread_checker_.IsCurrent());
90   return 0;
91 }
92 
Terminate()93 int32_t AudioRecordJni::Terminate() {
94   RTC_LOG(LS_INFO) << "Terminate";
95   RTC_DCHECK(thread_checker_.IsCurrent());
96   StopRecording();
97   thread_checker_.Detach();
98   return 0;
99 }
100 
InitRecording()101 int32_t AudioRecordJni::InitRecording() {
102   RTC_LOG(LS_INFO) << "InitRecording";
103   RTC_DCHECK(thread_checker_.IsCurrent());
104   if (initialized_) {
105     // Already initialized.
106     return 0;
107   }
108   RTC_DCHECK(!recording_);
109   ScopedHistogramTimer timer("WebRTC.Audio.InitRecordingDurationMs");
110 
111   int frames_per_buffer = Java_WebRtcAudioRecord_initRecording(
112       env_, j_audio_record_, audio_parameters_.sample_rate(),
113       static_cast<int>(audio_parameters_.channels()));
114   if (frames_per_buffer < 0) {
115     direct_buffer_address_ = nullptr;
116     RTC_LOG(LS_ERROR) << "InitRecording failed";
117     return -1;
118   }
119   frames_per_buffer_ = static_cast<size_t>(frames_per_buffer);
120   RTC_LOG(LS_INFO) << "frames_per_buffer: " << frames_per_buffer_;
121   const size_t bytes_per_frame = audio_parameters_.channels() * sizeof(int16_t);
122   RTC_CHECK_EQ(direct_buffer_capacity_in_bytes_,
123                frames_per_buffer_ * bytes_per_frame);
124   RTC_CHECK_EQ(frames_per_buffer_, audio_parameters_.frames_per_10ms_buffer());
125   initialized_ = true;
126   return 0;
127 }
128 
RecordingIsInitialized() const129 bool AudioRecordJni::RecordingIsInitialized() const {
130   return initialized_;
131 }
132 
StartRecording()133 int32_t AudioRecordJni::StartRecording() {
134   RTC_LOG(LS_INFO) << "StartRecording";
135   RTC_DCHECK(thread_checker_.IsCurrent());
136   if (recording_) {
137     // Already recording.
138     return 0;
139   }
140   if (!initialized_) {
141     RTC_DLOG(LS_WARNING)
142         << "Recording can not start since InitRecording must succeed first";
143     return 0;
144   }
145   ScopedHistogramTimer timer("WebRTC.Audio.StartRecordingDurationMs");
146   if (!Java_WebRtcAudioRecord_startRecording(env_, j_audio_record_)) {
147     RTC_LOG(LS_ERROR) << "StartRecording failed";
148     return -1;
149   }
150   recording_ = true;
151   return 0;
152 }
153 
StopRecording()154 int32_t AudioRecordJni::StopRecording() {
155   RTC_LOG(LS_INFO) << "StopRecording";
156   RTC_DCHECK(thread_checker_.IsCurrent());
157   if (!initialized_ || !recording_) {
158     return 0;
159   }
160   // Check if the audio source matched the activated recording session but only
161   // if a valid results exists to avoid invalid statistics.
162   if (Java_WebRtcAudioRecord_isAudioConfigVerified(env_, j_audio_record_)) {
163     const bool session_was_ok =
164         Java_WebRtcAudioRecord_isAudioSourceMatchingRecordingSession(
165             env_, j_audio_record_);
166     RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.SourceMatchesRecordingSession",
167                           session_was_ok);
168     RTC_LOG(LS_INFO)
169         << "HISTOGRAM(WebRTC.Audio.SourceMatchesRecordingSession): "
170         << session_was_ok;
171   }
172   if (!Java_WebRtcAudioRecord_stopRecording(env_, j_audio_record_)) {
173     RTC_LOG(LS_ERROR) << "StopRecording failed";
174     return -1;
175   }
176   // If we don't detach here, we will hit a RTC_DCHECK in OnDataIsRecorded()
177   // next time StartRecording() is called since it will create a new Java
178   // thread.
179   thread_checker_java_.Detach();
180   initialized_ = false;
181   recording_ = false;
182   direct_buffer_address_ = nullptr;
183   return 0;
184 }
185 
Recording() const186 bool AudioRecordJni::Recording() const {
187   return recording_;
188 }
189 
AttachAudioBuffer(AudioDeviceBuffer * audioBuffer)190 void AudioRecordJni::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
191   RTC_LOG(LS_INFO) << "AttachAudioBuffer";
192   RTC_DCHECK(thread_checker_.IsCurrent());
193   audio_device_buffer_ = audioBuffer;
194   const int sample_rate_hz = audio_parameters_.sample_rate();
195   RTC_LOG(LS_INFO) << "SetRecordingSampleRate(" << sample_rate_hz << ")";
196   audio_device_buffer_->SetRecordingSampleRate(sample_rate_hz);
197   const size_t channels = audio_parameters_.channels();
198   RTC_LOG(LS_INFO) << "SetRecordingChannels(" << channels << ")";
199   audio_device_buffer_->SetRecordingChannels(channels);
200 }
201 
IsAcousticEchoCancelerSupported() const202 bool AudioRecordJni::IsAcousticEchoCancelerSupported() const {
203   RTC_DCHECK(thread_checker_.IsCurrent());
204   return Java_WebRtcAudioRecord_isAcousticEchoCancelerSupported(
205       env_, j_audio_record_);
206 }
207 
IsNoiseSuppressorSupported() const208 bool AudioRecordJni::IsNoiseSuppressorSupported() const {
209   RTC_DCHECK(thread_checker_.IsCurrent());
210   return Java_WebRtcAudioRecord_isNoiseSuppressorSupported(env_,
211                                                            j_audio_record_);
212 }
213 
EnableBuiltInAEC(bool enable)214 int32_t AudioRecordJni::EnableBuiltInAEC(bool enable) {
215   RTC_LOG(LS_INFO) << "EnableBuiltInAEC(" << enable << ")";
216   RTC_DCHECK(thread_checker_.IsCurrent());
217   return Java_WebRtcAudioRecord_enableBuiltInAEC(env_, j_audio_record_, enable)
218              ? 0
219              : -1;
220 }
221 
EnableBuiltInNS(bool enable)222 int32_t AudioRecordJni::EnableBuiltInNS(bool enable) {
223   RTC_LOG(LS_INFO) << "EnableBuiltInNS(" << enable << ")";
224   RTC_DCHECK(thread_checker_.IsCurrent());
225   return Java_WebRtcAudioRecord_enableBuiltInNS(env_, j_audio_record_, enable)
226              ? 0
227              : -1;
228 }
229 
CacheDirectBufferAddress(JNIEnv * env,const JavaParamRef<jobject> & j_caller,const JavaParamRef<jobject> & byte_buffer)230 void AudioRecordJni::CacheDirectBufferAddress(
231     JNIEnv* env,
232     const JavaParamRef<jobject>& j_caller,
233     const JavaParamRef<jobject>& byte_buffer) {
234   RTC_LOG(LS_INFO) << "OnCacheDirectBufferAddress";
235   RTC_DCHECK(thread_checker_.IsCurrent());
236   RTC_DCHECK(!direct_buffer_address_);
237   direct_buffer_address_ = env->GetDirectBufferAddress(byte_buffer.obj());
238   jlong capacity = env->GetDirectBufferCapacity(byte_buffer.obj());
239   RTC_LOG(LS_INFO) << "direct buffer capacity: " << capacity;
240   direct_buffer_capacity_in_bytes_ = static_cast<size_t>(capacity);
241 }
242 
243 // This method is called on a high-priority thread from Java. The name of
244 // the thread is 'AudioRecordThread'.
DataIsRecorded(JNIEnv * env,const JavaParamRef<jobject> & j_caller,int length,int64_t capture_timestamp_ns)245 void AudioRecordJni::DataIsRecorded(JNIEnv* env,
246                                     const JavaParamRef<jobject>& j_caller,
247                                     int length,
248                                     int64_t capture_timestamp_ns) {
249   RTC_DCHECK(thread_checker_java_.IsCurrent());
250   if (!audio_device_buffer_) {
251     RTC_LOG(LS_ERROR) << "AttachAudioBuffer has not been called";
252     return;
253   }
254   audio_device_buffer_->SetRecordedBuffer(
255       direct_buffer_address_, frames_per_buffer_, capture_timestamp_ns);
256   // We provide one (combined) fixed delay estimate for the APM and use the
257   // `playDelayMs` parameter only. Components like the AEC only sees the sum
258   // of `playDelayMs` and `recDelayMs`, hence the distributions does not matter.
259   audio_device_buffer_->SetVQEData(total_delay_ms_, 0);
260   if (audio_device_buffer_->DeliverRecordedData() == -1) {
261     RTC_LOG(LS_INFO) << "AudioDeviceBuffer::DeliverRecordedData failed";
262   }
263 }
264 
265 }  // namespace jni
266 
267 }  // namespace webrtc
268