blob: 7dfc5ec891226709a99e6becad6f30206380c131 [file] [log] [blame]
henrikab2619892015-05-18 16:49:16 +02001/*
2 * Copyright (c) 2015 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 "webrtc/modules/audio_device/android/opensles_player.h"
12
13#include <android/log.h>
14
15#include "webrtc/base/arraysize.h"
16#include "webrtc/base/checks.h"
Peter Kasting1380e262015-08-28 17:31:03 -070017#include "webrtc/base/format_macros.h"
henrikae71b24e2015-11-12 01:48:32 -080018#include "webrtc/base/timeutils.h"
henrika521f7a82016-05-31 07:03:17 -070019#include "webrtc/modules/audio_device/android/audio_common.h"
henrikab2619892015-05-18 16:49:16 +020020#include "webrtc/modules/audio_device/android/audio_manager.h"
henrika86d907c2015-09-07 16:09:50 +020021#include "webrtc/modules/audio_device/fine_audio_buffer.h"
henrikab2619892015-05-18 16:49:16 +020022
23#define TAG "OpenSLESPlayer"
24#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
25#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
26#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
27#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
28#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
29
henrika521f7a82016-05-31 07:03:17 -070030#define RETURN_ON_ERROR(op, ...) \
31 do { \
32 SLresult err = (op); \
33 if (err != SL_RESULT_SUCCESS) { \
34 ALOGE("%s failed: %s", #op, GetSLErrorString(err)); \
35 return __VA_ARGS__; \
36 } \
henrikab2619892015-05-18 16:49:16 +020037 } while (0)
38
39namespace webrtc {
40
41OpenSLESPlayer::OpenSLESPlayer(AudioManager* audio_manager)
henrika521f7a82016-05-31 07:03:17 -070042 : audio_manager_(audio_manager),
43 audio_parameters_(audio_manager->GetPlayoutAudioParameters()),
44 audio_device_buffer_(nullptr),
henrikab2619892015-05-18 16:49:16 +020045 initialized_(false),
46 playing_(false),
henrikab2619892015-05-18 16:49:16 +020047 buffer_index_(0),
48 engine_(nullptr),
49 player_(nullptr),
50 simple_buffer_queue_(nullptr),
henrikae71b24e2015-11-12 01:48:32 -080051 volume_(nullptr),
52 last_play_time_(0) {
henrikab2619892015-05-18 16:49:16 +020053 ALOGD("ctor%s", GetThreadInfo().c_str());
54 // Use native audio output parameters provided by the audio manager and
55 // define the PCM format structure.
56 pcm_format_ = CreatePCMConfiguration(audio_parameters_.channels(),
57 audio_parameters_.sample_rate(),
58 audio_parameters_.bits_per_sample());
59 // Detach from this thread since we want to use the checker to verify calls
60 // from the internal audio thread.
61 thread_checker_opensles_.DetachFromThread();
62}
63
64OpenSLESPlayer::~OpenSLESPlayer() {
65 ALOGD("dtor%s", GetThreadInfo().c_str());
henrikg91d6ede2015-09-17 00:24:34 -070066 RTC_DCHECK(thread_checker_.CalledOnValidThread());
henrikab2619892015-05-18 16:49:16 +020067 Terminate();
68 DestroyAudioPlayer();
69 DestroyMix();
henrika521f7a82016-05-31 07:03:17 -070070 engine_ = nullptr;
henrikg91d6ede2015-09-17 00:24:34 -070071 RTC_DCHECK(!engine_);
72 RTC_DCHECK(!output_mix_.Get());
73 RTC_DCHECK(!player_);
74 RTC_DCHECK(!simple_buffer_queue_);
75 RTC_DCHECK(!volume_);
henrikab2619892015-05-18 16:49:16 +020076}
77
78int OpenSLESPlayer::Init() {
79 ALOGD("Init%s", GetThreadInfo().c_str());
henrikg91d6ede2015-09-17 00:24:34 -070080 RTC_DCHECK(thread_checker_.CalledOnValidThread());
henrikab2619892015-05-18 16:49:16 +020081 return 0;
82}
83
84int OpenSLESPlayer::Terminate() {
85 ALOGD("Terminate%s", GetThreadInfo().c_str());
henrikg91d6ede2015-09-17 00:24:34 -070086 RTC_DCHECK(thread_checker_.CalledOnValidThread());
henrikab2619892015-05-18 16:49:16 +020087 StopPlayout();
88 return 0;
89}
90
91int OpenSLESPlayer::InitPlayout() {
92 ALOGD("InitPlayout%s", GetThreadInfo().c_str());
henrikg91d6ede2015-09-17 00:24:34 -070093 RTC_DCHECK(thread_checker_.CalledOnValidThread());
94 RTC_DCHECK(!initialized_);
95 RTC_DCHECK(!playing_);
henrika918b5542016-09-19 15:44:09 +020096 if (!ObtainEngineInterface()) {
97 ALOGE("Failed to obtain SL Engine interface");
98 return -1;
99 }
henrikab2619892015-05-18 16:49:16 +0200100 CreateMix();
101 initialized_ = true;
102 buffer_index_ = 0;
103 return 0;
104}
105
106int OpenSLESPlayer::StartPlayout() {
107 ALOGD("StartPlayout%s", GetThreadInfo().c_str());
henrikg91d6ede2015-09-17 00:24:34 -0700108 RTC_DCHECK(thread_checker_.CalledOnValidThread());
109 RTC_DCHECK(initialized_);
110 RTC_DCHECK(!playing_);
henrika918b5542016-09-19 15:44:09 +0200111 if (fine_audio_buffer_) {
112 fine_audio_buffer_->ResetPlayout();
113 }
henrikab2619892015-05-18 16:49:16 +0200114 // The number of lower latency audio players is limited, hence we create the
115 // audio player in Start() and destroy it in Stop().
116 CreateAudioPlayer();
117 // Fill up audio buffers to avoid initial glitch and to ensure that playback
118 // starts when mode is later changed to SL_PLAYSTATE_PLAYING.
119 // TODO(henrika): we can save some delay by only making one call to
120 // EnqueuePlayoutData. Most likely not worth the risk of adding a glitch.
henrika918b5542016-09-19 15:44:09 +0200121 last_play_time_ = rtc::Time();
henrikab2619892015-05-18 16:49:16 +0200122 for (int i = 0; i < kNumOfOpenSLESBuffers; ++i) {
henrika14acf652016-10-11 06:15:41 -0700123 EnqueuePlayoutData(true);
henrikab2619892015-05-18 16:49:16 +0200124 }
125 // Start streaming data by setting the play state to SL_PLAYSTATE_PLAYING.
126 // For a player object, when the object is in the SL_PLAYSTATE_PLAYING
127 // state, adding buffers will implicitly start playback.
128 RETURN_ON_ERROR((*player_)->SetPlayState(player_, SL_PLAYSTATE_PLAYING), -1);
129 playing_ = (GetPlayState() == SL_PLAYSTATE_PLAYING);
henrikg91d6ede2015-09-17 00:24:34 -0700130 RTC_DCHECK(playing_);
henrikab2619892015-05-18 16:49:16 +0200131 return 0;
132}
133
134int OpenSLESPlayer::StopPlayout() {
135 ALOGD("StopPlayout%s", GetThreadInfo().c_str());
henrikg91d6ede2015-09-17 00:24:34 -0700136 RTC_DCHECK(thread_checker_.CalledOnValidThread());
henrikab2619892015-05-18 16:49:16 +0200137 if (!initialized_ || !playing_) {
138 return 0;
139 }
140 // Stop playing by setting the play state to SL_PLAYSTATE_STOPPED.
141 RETURN_ON_ERROR((*player_)->SetPlayState(player_, SL_PLAYSTATE_STOPPED), -1);
142 // Clear the buffer queue to flush out any remaining data.
143 RETURN_ON_ERROR((*simple_buffer_queue_)->Clear(simple_buffer_queue_), -1);
kwiberg5377bc72016-10-04 13:46:56 -0700144#if RTC_DCHECK_IS_ON
henrikab2619892015-05-18 16:49:16 +0200145 // Verify that the buffer queue is in fact cleared as it should.
146 SLAndroidSimpleBufferQueueState buffer_queue_state;
147 (*simple_buffer_queue_)->GetState(simple_buffer_queue_, &buffer_queue_state);
kwibergaf476c72016-11-28 15:21:39 -0800148 RTC_DCHECK_EQ(0, buffer_queue_state.count);
149 RTC_DCHECK_EQ(0, buffer_queue_state.index);
henrikab2619892015-05-18 16:49:16 +0200150#endif
151 // The number of lower latency audio players is limited, hence we create the
152 // audio player in Start() and destroy it in Stop().
153 DestroyAudioPlayer();
154 thread_checker_opensles_.DetachFromThread();
155 initialized_ = false;
156 playing_ = false;
157 return 0;
158}
159
160int OpenSLESPlayer::SpeakerVolumeIsAvailable(bool& available) {
161 available = false;
162 return 0;
163}
164
165int OpenSLESPlayer::MaxSpeakerVolume(uint32_t& maxVolume) const {
166 return -1;
167}
168
169int OpenSLESPlayer::MinSpeakerVolume(uint32_t& minVolume) const {
170 return -1;
171}
172
173int OpenSLESPlayer::SetSpeakerVolume(uint32_t volume) {
174 return -1;
175}
176
177int OpenSLESPlayer::SpeakerVolume(uint32_t& volume) const {
178 return -1;
179}
180
181void OpenSLESPlayer::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
182 ALOGD("AttachAudioBuffer");
henrikg91d6ede2015-09-17 00:24:34 -0700183 RTC_DCHECK(thread_checker_.CalledOnValidThread());
henrikab2619892015-05-18 16:49:16 +0200184 audio_device_buffer_ = audioBuffer;
185 const int sample_rate_hz = audio_parameters_.sample_rate();
186 ALOGD("SetPlayoutSampleRate(%d)", sample_rate_hz);
187 audio_device_buffer_->SetPlayoutSampleRate(sample_rate_hz);
Peter Kasting69558702016-01-12 16:26:35 -0800188 const size_t channels = audio_parameters_.channels();
189 ALOGD("SetPlayoutChannels(%" PRIuS ")", channels);
henrikab2619892015-05-18 16:49:16 +0200190 audio_device_buffer_->SetPlayoutChannels(channels);
henrikg91d6ede2015-09-17 00:24:34 -0700191 RTC_CHECK(audio_device_buffer_);
henrikab2619892015-05-18 16:49:16 +0200192 AllocateDataBuffers();
193}
194
henrikab2619892015-05-18 16:49:16 +0200195void OpenSLESPlayer::AllocateDataBuffers() {
196 ALOGD("AllocateDataBuffers");
henrikg91d6ede2015-09-17 00:24:34 -0700197 RTC_DCHECK(thread_checker_.CalledOnValidThread());
198 RTC_DCHECK(!simple_buffer_queue_);
199 RTC_CHECK(audio_device_buffer_);
henrikab2619892015-05-18 16:49:16 +0200200 // Create a modified audio buffer class which allows us to ask for any number
201 // of samples (and not only multiple of 10ms) to match the native OpenSL ES
henrika918b5542016-09-19 15:44:09 +0200202 // buffer size. The native buffer size corresponds to the
203 // PROPERTY_OUTPUT_FRAMES_PER_BUFFER property which is the number of audio
204 // frames that the HAL (Hardware Abstraction Layer) buffer can hold. It is
205 // recommended to construct audio buffers so that they contain an exact
206 // multiple of this number. If so, callbacks will occur at regular intervals,
207 // which reduces jitter.
208 ALOGD("native buffer size: %" PRIuS, audio_parameters_.GetBytesPerBuffer());
209 ALOGD("native buffer size in ms: %.2f",
210 audio_parameters_.GetBufferSizeInMilliseconds());
211 fine_audio_buffer_.reset(new FineAudioBuffer(
212 audio_device_buffer_, audio_parameters_.GetBytesPerBuffer(),
213 audio_parameters_.sample_rate()));
henrikab2619892015-05-18 16:49:16 +0200214 // Each buffer must be of this size to avoid unnecessary memcpy while caching
215 // data between successive callbacks.
henrika86d907c2015-09-07 16:09:50 +0200216 const size_t required_buffer_size =
henrika918b5542016-09-19 15:44:09 +0200217 fine_audio_buffer_->RequiredPlayoutBufferSizeBytes();
Peter Kasting1380e262015-08-28 17:31:03 -0700218 ALOGD("required buffer size: %" PRIuS, required_buffer_size);
henrikab2619892015-05-18 16:49:16 +0200219 for (int i = 0; i < kNumOfOpenSLESBuffers; ++i) {
220 audio_buffers_[i].reset(new SLint8[required_buffer_size]);
221 }
222}
223
henrika521f7a82016-05-31 07:03:17 -0700224bool OpenSLESPlayer::ObtainEngineInterface() {
225 ALOGD("ObtainEngineInterface");
henrikg91d6ede2015-09-17 00:24:34 -0700226 RTC_DCHECK(thread_checker_.CalledOnValidThread());
henrika918b5542016-09-19 15:44:09 +0200227 if (engine_)
228 return true;
henrika521f7a82016-05-31 07:03:17 -0700229 // Get access to (or create if not already existing) the global OpenSL Engine
230 // object.
231 SLObjectItf engine_object = audio_manager_->GetOpenSLEngine();
232 if (engine_object == nullptr) {
233 ALOGE("Failed to access the global OpenSL engine");
234 return false;
235 }
236 // Get the SL Engine Interface which is implicit.
henrikab2619892015-05-18 16:49:16 +0200237 RETURN_ON_ERROR(
henrika521f7a82016-05-31 07:03:17 -0700238 (*engine_object)->GetInterface(engine_object, SL_IID_ENGINE, &engine_),
henrikab2619892015-05-18 16:49:16 +0200239 false);
henrikab2619892015-05-18 16:49:16 +0200240 return true;
241}
242
henrikab2619892015-05-18 16:49:16 +0200243bool OpenSLESPlayer::CreateMix() {
244 ALOGD("CreateMix");
henrikg91d6ede2015-09-17 00:24:34 -0700245 RTC_DCHECK(thread_checker_.CalledOnValidThread());
246 RTC_DCHECK(engine_);
henrikab2619892015-05-18 16:49:16 +0200247 if (output_mix_.Get())
248 return true;
249
250 // Create the ouput mix on the engine object. No interfaces will be used.
251 RETURN_ON_ERROR((*engine_)->CreateOutputMix(engine_, output_mix_.Receive(), 0,
henrika521f7a82016-05-31 07:03:17 -0700252 nullptr, nullptr),
henrikab2619892015-05-18 16:49:16 +0200253 false);
254 RETURN_ON_ERROR(output_mix_->Realize(output_mix_.Get(), SL_BOOLEAN_FALSE),
255 false);
256 return true;
257}
258
259void OpenSLESPlayer::DestroyMix() {
260 ALOGD("DestroyMix");
henrikg91d6ede2015-09-17 00:24:34 -0700261 RTC_DCHECK(thread_checker_.CalledOnValidThread());
henrikab2619892015-05-18 16:49:16 +0200262 if (!output_mix_.Get())
263 return;
264 output_mix_.Reset();
265}
266
267bool OpenSLESPlayer::CreateAudioPlayer() {
268 ALOGD("CreateAudioPlayer");
henrikg91d6ede2015-09-17 00:24:34 -0700269 RTC_DCHECK(thread_checker_.CalledOnValidThread());
henrikg91d6ede2015-09-17 00:24:34 -0700270 RTC_DCHECK(output_mix_.Get());
henrikab2619892015-05-18 16:49:16 +0200271 if (player_object_.Get())
272 return true;
henrikg91d6ede2015-09-17 00:24:34 -0700273 RTC_DCHECK(!player_);
274 RTC_DCHECK(!simple_buffer_queue_);
275 RTC_DCHECK(!volume_);
henrikab2619892015-05-18 16:49:16 +0200276
277 // source: Android Simple Buffer Queue Data Locator is source.
278 SLDataLocator_AndroidSimpleBufferQueue simple_buffer_queue = {
279 SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
280 static_cast<SLuint32>(kNumOfOpenSLESBuffers)};
281 SLDataSource audio_source = {&simple_buffer_queue, &pcm_format_};
282
283 // sink: OutputMix-based data is sink.
284 SLDataLocator_OutputMix locator_output_mix = {SL_DATALOCATOR_OUTPUTMIX,
285 output_mix_.Get()};
henrika521f7a82016-05-31 07:03:17 -0700286 SLDataSink audio_sink = {&locator_output_mix, nullptr};
henrikab2619892015-05-18 16:49:16 +0200287
288 // Define interfaces that we indend to use and realize.
289 const SLInterfaceID interface_ids[] = {
290 SL_IID_ANDROIDCONFIGURATION, SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
291 const SLboolean interface_required[] = {
292 SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
293
294 // Create the audio player on the engine interface.
295 RETURN_ON_ERROR(
296 (*engine_)->CreateAudioPlayer(
297 engine_, player_object_.Receive(), &audio_source, &audio_sink,
298 arraysize(interface_ids), interface_ids, interface_required),
299 false);
300
301 // Use the Android configuration interface to set platform-specific
302 // parameters. Should be done before player is realized.
303 SLAndroidConfigurationItf player_config;
304 RETURN_ON_ERROR(
305 player_object_->GetInterface(player_object_.Get(),
306 SL_IID_ANDROIDCONFIGURATION, &player_config),
307 false);
308 // Set audio player configuration to SL_ANDROID_STREAM_VOICE which
309 // corresponds to android.media.AudioManager.STREAM_VOICE_CALL.
henrika1ba936a2015-11-03 04:27:58 -0800310 SLint32 stream_type = SL_ANDROID_STREAM_VOICE;
henrikab2619892015-05-18 16:49:16 +0200311 RETURN_ON_ERROR(
312 (*player_config)
313 ->SetConfiguration(player_config, SL_ANDROID_KEY_STREAM_TYPE,
314 &stream_type, sizeof(SLint32)),
315 false);
316
317 // Realize the audio player object after configuration has been set.
318 RETURN_ON_ERROR(
319 player_object_->Realize(player_object_.Get(), SL_BOOLEAN_FALSE), false);
320
321 // Get the SLPlayItf interface on the audio player.
322 RETURN_ON_ERROR(
323 player_object_->GetInterface(player_object_.Get(), SL_IID_PLAY, &player_),
324 false);
325
326 // Get the SLAndroidSimpleBufferQueueItf interface on the audio player.
327 RETURN_ON_ERROR(
328 player_object_->GetInterface(player_object_.Get(), SL_IID_BUFFERQUEUE,
329 &simple_buffer_queue_),
330 false);
331
332 // Register callback method for the Android Simple Buffer Queue interface.
333 // This method will be called when the native audio layer needs audio data.
334 RETURN_ON_ERROR((*simple_buffer_queue_)
335 ->RegisterCallback(simple_buffer_queue_,
336 SimpleBufferQueueCallback, this),
337 false);
338
339 // Get the SLVolumeItf interface on the audio player.
340 RETURN_ON_ERROR(player_object_->GetInterface(player_object_.Get(),
341 SL_IID_VOLUME, &volume_),
342 false);
343
344 // TODO(henrika): might not be required to set volume to max here since it
345 // seems to be default on most devices. Might be required for unit tests.
346 // RETURN_ON_ERROR((*volume_)->SetVolumeLevel(volume_, 0), false);
347
348 return true;
349}
350
351void OpenSLESPlayer::DestroyAudioPlayer() {
352 ALOGD("DestroyAudioPlayer");
henrikg91d6ede2015-09-17 00:24:34 -0700353 RTC_DCHECK(thread_checker_.CalledOnValidThread());
henrikab2619892015-05-18 16:49:16 +0200354 if (!player_object_.Get())
355 return;
henrika918b5542016-09-19 15:44:09 +0200356 (*simple_buffer_queue_)
357 ->RegisterCallback(simple_buffer_queue_, nullptr, nullptr);
henrikab2619892015-05-18 16:49:16 +0200358 player_object_.Reset();
359 player_ = nullptr;
360 simple_buffer_queue_ = nullptr;
361 volume_ = nullptr;
362}
363
364// static
365void OpenSLESPlayer::SimpleBufferQueueCallback(
366 SLAndroidSimpleBufferQueueItf caller,
367 void* context) {
368 OpenSLESPlayer* stream = reinterpret_cast<OpenSLESPlayer*>(context);
369 stream->FillBufferQueue();
370}
371
372void OpenSLESPlayer::FillBufferQueue() {
henrikg91d6ede2015-09-17 00:24:34 -0700373 RTC_DCHECK(thread_checker_opensles_.CalledOnValidThread());
henrikab2619892015-05-18 16:49:16 +0200374 SLuint32 state = GetPlayState();
375 if (state != SL_PLAYSTATE_PLAYING) {
376 ALOGW("Buffer callback in non-playing state!");
377 return;
378 }
henrika14acf652016-10-11 06:15:41 -0700379 EnqueuePlayoutData(false);
henrikab2619892015-05-18 16:49:16 +0200380}
381
henrika14acf652016-10-11 06:15:41 -0700382void OpenSLESPlayer::EnqueuePlayoutData(bool silence) {
henrikae71b24e2015-11-12 01:48:32 -0800383 // Check delta time between two successive callbacks and provide a warning
384 // if it becomes very large.
henrika918b5542016-09-19 15:44:09 +0200385 // TODO(henrika): using 150ms as upper limit but this value is rather random.
henrikae71b24e2015-11-12 01:48:32 -0800386 const uint32_t current_time = rtc::Time();
387 const uint32_t diff = current_time - last_play_time_;
henrika918b5542016-09-19 15:44:09 +0200388 if (diff > 150) {
henrikae71b24e2015-11-12 01:48:32 -0800389 ALOGW("Bad OpenSL ES playout timing, dT=%u [ms]", diff);
390 }
391 last_play_time_ = current_time;
henrikab2619892015-05-18 16:49:16 +0200392 SLint8* audio_ptr = audio_buffers_[buffer_index_].get();
henrika14acf652016-10-11 06:15:41 -0700393 if (silence) {
394 RTC_DCHECK(thread_checker_.CalledOnValidThread());
395 // Avoid aquiring real audio data from WebRTC and fill the buffer with
396 // zeros instead. Used to prime the buffer with silence and to avoid asking
397 // for audio data from two different threads.
398 memset(audio_ptr, 0, audio_parameters_.GetBytesPerBuffer());
399 } else {
400 RTC_DCHECK(thread_checker_opensles_.CalledOnValidThread());
401 // Read audio data from the WebRTC source using the FineAudioBuffer object
402 // to adjust for differences in buffer size between WebRTC (10ms) and native
403 // OpenSL ES.
404 fine_audio_buffer_->GetPlayoutData(audio_ptr);
405 }
henrikab2619892015-05-18 16:49:16 +0200406 // Enqueue the decoded audio buffer for playback.
henrika918b5542016-09-19 15:44:09 +0200407 SLresult err = (*simple_buffer_queue_)
408 ->Enqueue(simple_buffer_queue_, audio_ptr,
409 audio_parameters_.GetBytesPerBuffer());
henrikab2619892015-05-18 16:49:16 +0200410 if (SL_RESULT_SUCCESS != err) {
411 ALOGE("Enqueue failed: %d", err);
412 }
413 buffer_index_ = (buffer_index_ + 1) % kNumOfOpenSLESBuffers;
414}
415
416SLuint32 OpenSLESPlayer::GetPlayState() const {
henrikg91d6ede2015-09-17 00:24:34 -0700417 RTC_DCHECK(player_);
henrikab2619892015-05-18 16:49:16 +0200418 SLuint32 state;
419 SLresult err = (*player_)->GetPlayState(player_, &state);
420 if (SL_RESULT_SUCCESS != err) {
421 ALOGE("GetPlayState failed: %d", err);
422 }
423 return state;
424}
425
426} // namespace webrtc