
bind.h, callback.h, callback_forward.h, and callback_helpers.h moved into /base/functional/. Update the include paths to directly include them in their new location. Bug: 1364441 Change-Id: I36784c7cc322001d0b0ec2590c3847f4298033ad Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4157596 Owners-Override: Avi Drissman <avi@chromium.org> Reviewed-by: Daniel Cheng <dcheng@chromium.org> Commit-Queue: Daniel Cheng <dcheng@chromium.org> Owners-Override: Daniel Cheng <dcheng@chromium.org> Auto-Submit: Avi Drissman <avi@chromium.org> Cr-Commit-Position: refs/heads/main@{#1091526}
323 lines
11 KiB
C++
323 lines
11 KiB
C++
// Copyright 2017 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "ppapi/proxy/audio_output_resource.h"
|
|
|
|
#include <memory>
|
|
|
|
#include "base/check_op.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/numerics/safe_conversions.h"
|
|
#include "ipc/ipc_platform_file.h"
|
|
#include "media/base/audio_bus.h"
|
|
#include "media/base/audio_parameters.h"
|
|
#include "ppapi/c/pp_errors.h"
|
|
#include "ppapi/proxy/ppapi_messages.h"
|
|
#include "ppapi/proxy/resource_message_params.h"
|
|
#include "ppapi/proxy/serialized_handle.h"
|
|
#include "ppapi/shared_impl/ppapi_globals.h"
|
|
#include "ppapi/shared_impl/ppb_audio_config_shared.h"
|
|
#include "ppapi/shared_impl/resource_tracker.h"
|
|
#include "ppapi/shared_impl/tracked_callback.h"
|
|
#include "ppapi/thunk/enter.h"
|
|
#include "ppapi/thunk/ppb_audio_config_api.h"
|
|
|
|
namespace ppapi {
|
|
namespace proxy {
|
|
|
|
AudioOutputResource::AudioOutputResource(Connection connection,
|
|
PP_Instance instance)
|
|
: PluginResource(connection, instance),
|
|
open_state_(BEFORE_OPEN),
|
|
playing_(false),
|
|
shared_memory_size_(0),
|
|
audio_output_callback_(NULL),
|
|
user_data_(NULL),
|
|
enumeration_helper_(this),
|
|
bytes_per_second_(0),
|
|
sample_frame_count_(0),
|
|
client_buffer_size_bytes_(0) {
|
|
SendCreate(RENDERER, PpapiHostMsg_AudioOutput_Create());
|
|
}
|
|
|
|
AudioOutputResource::~AudioOutputResource() {
|
|
Close();
|
|
}
|
|
|
|
thunk::PPB_AudioOutput_API* AudioOutputResource::AsPPB_AudioOutput_API() {
|
|
return this;
|
|
}
|
|
|
|
void AudioOutputResource::OnReplyReceived(
|
|
const ResourceMessageReplyParams& params,
|
|
const IPC::Message& msg) {
|
|
if (!enumeration_helper_.HandleReply(params, msg))
|
|
PluginResource::OnReplyReceived(params, msg);
|
|
}
|
|
|
|
int32_t AudioOutputResource::EnumerateDevices(
|
|
const PP_ArrayOutput& output,
|
|
scoped_refptr<TrackedCallback> callback) {
|
|
return enumeration_helper_.EnumerateDevices(output, callback);
|
|
}
|
|
|
|
int32_t AudioOutputResource::MonitorDeviceChange(
|
|
PP_MonitorDeviceChangeCallback callback,
|
|
void* user_data) {
|
|
return enumeration_helper_.MonitorDeviceChange(callback, user_data);
|
|
}
|
|
|
|
int32_t AudioOutputResource::Open(
|
|
PP_Resource device_ref,
|
|
PP_Resource config,
|
|
PPB_AudioOutput_Callback audio_output_callback,
|
|
void* user_data,
|
|
scoped_refptr<TrackedCallback> callback) {
|
|
return CommonOpen(device_ref, config, audio_output_callback, user_data,
|
|
callback);
|
|
}
|
|
|
|
PP_Resource AudioOutputResource::GetCurrentConfig() {
|
|
// AddRef for the caller.
|
|
if (config_.get())
|
|
PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(config_);
|
|
return config_;
|
|
}
|
|
|
|
PP_Bool AudioOutputResource::StartPlayback() {
|
|
if (open_state_ == CLOSED || (open_state_ == BEFORE_OPEN &&
|
|
!TrackedCallback::IsPending(open_callback_))) {
|
|
return PP_FALSE;
|
|
}
|
|
if (playing_)
|
|
return PP_TRUE;
|
|
|
|
playing_ = true;
|
|
|
|
StartThread();
|
|
|
|
Post(RENDERER, PpapiHostMsg_AudioOutput_StartOrStop(true));
|
|
return PP_TRUE;
|
|
}
|
|
|
|
PP_Bool AudioOutputResource::StopPlayback() {
|
|
if (open_state_ == CLOSED)
|
|
return PP_FALSE;
|
|
if (!playing_)
|
|
return PP_TRUE;
|
|
|
|
// If the audio output device hasn't been opened, set |playing_| to false and
|
|
// return directly.
|
|
if (open_state_ == BEFORE_OPEN) {
|
|
playing_ = false;
|
|
return PP_TRUE;
|
|
}
|
|
|
|
Post(RENDERER, PpapiHostMsg_AudioOutput_StartOrStop(false));
|
|
|
|
StopThread();
|
|
playing_ = false;
|
|
|
|
return PP_TRUE;
|
|
}
|
|
|
|
void AudioOutputResource::Close() {
|
|
if (open_state_ == CLOSED)
|
|
return;
|
|
|
|
open_state_ = CLOSED;
|
|
Post(RENDERER, PpapiHostMsg_AudioOutput_Close());
|
|
StopThread();
|
|
|
|
if (TrackedCallback::IsPending(open_callback_))
|
|
open_callback_->PostAbort();
|
|
}
|
|
|
|
void AudioOutputResource::LastPluginRefWasDeleted() {
|
|
enumeration_helper_.LastPluginRefWasDeleted();
|
|
}
|
|
|
|
void AudioOutputResource::OnPluginMsgOpenReply(
|
|
const ResourceMessageReplyParams& params) {
|
|
if (open_state_ == BEFORE_OPEN && params.result() == PP_OK) {
|
|
IPC::PlatformFileForTransit socket_handle_for_transit =
|
|
IPC::InvalidPlatformFileForTransit();
|
|
params.TakeSocketHandleAtIndex(0, &socket_handle_for_transit);
|
|
base::SyncSocket::Handle socket_handle =
|
|
IPC::PlatformFileForTransitToPlatformFile(socket_handle_for_transit);
|
|
CHECK(socket_handle != base::SyncSocket::kInvalidHandle);
|
|
|
|
SerializedHandle serialized_shared_memory_handle =
|
|
params.TakeHandleOfTypeAtIndex(1,
|
|
SerializedHandle::SHARED_MEMORY_REGION);
|
|
CHECK(serialized_shared_memory_handle.IsHandleValid());
|
|
|
|
open_state_ = OPENED;
|
|
SetStreamInfo(base::UnsafeSharedMemoryRegion::Deserialize(
|
|
serialized_shared_memory_handle.TakeSharedMemoryRegion()),
|
|
socket_handle);
|
|
} else {
|
|
playing_ = false;
|
|
}
|
|
|
|
// The callback may have been aborted by Close().
|
|
if (TrackedCallback::IsPending(open_callback_))
|
|
open_callback_->Run(params.result());
|
|
}
|
|
|
|
void AudioOutputResource::SetStreamInfo(
|
|
base::UnsafeSharedMemoryRegion shared_memory_region,
|
|
base::SyncSocket::Handle socket_handle) {
|
|
socket_ = std::make_unique<base::CancelableSyncSocket>(socket_handle);
|
|
|
|
// Ensure that the allocated memory is enough for the audio bus and buffer
|
|
// parameters. Note that there might be slightly more allocated memory as
|
|
// some shared memory implementations round up to the closest 2^n when
|
|
// allocating.
|
|
// Example: DCHECK_GE(8208, 8192 + 16) for |sample_frame_count_| = 2048.
|
|
shared_memory_size_ = media::ComputeAudioOutputBufferSize(
|
|
kAudioOutputChannels, sample_frame_count_);
|
|
DCHECK_GE(shared_memory_region.GetSize(), shared_memory_size_);
|
|
|
|
// If we fail to map the shared memory into the caller's address space we
|
|
// might as well fail here since nothing will work if this is the case.
|
|
shared_memory_mapping_ = shared_memory_region.MapAt(0, shared_memory_size_);
|
|
CHECK(shared_memory_mapping_.IsValid());
|
|
|
|
// Create a new audio bus and wrap the audio data section in shared memory.
|
|
media::AudioOutputBuffer* buffer =
|
|
static_cast<media::AudioOutputBuffer*>(shared_memory_mapping_.memory());
|
|
audio_bus_ = media::AudioBus::WrapMemory(kAudioOutputChannels,
|
|
sample_frame_count_, buffer->audio);
|
|
|
|
// Setup integer audio buffer for user audio data
|
|
client_buffer_size_bytes_ = audio_bus_->frames() * audio_bus_->channels() *
|
|
kBitsPerAudioOutputSample / 8;
|
|
client_buffer_.reset(new uint8_t[client_buffer_size_bytes_]);
|
|
}
|
|
|
|
void AudioOutputResource::StartThread() {
|
|
// Don't start the thread unless all our state is set up correctly.
|
|
if (!audio_output_callback_ || !socket_.get() ||
|
|
!shared_memory_mapping_.memory() || !audio_bus_.get() ||
|
|
!client_buffer_.get() || bytes_per_second_ == 0)
|
|
return;
|
|
|
|
// Clear contents of shm buffer before starting audio thread. This will
|
|
// prevent a burst of static if for some reason the audio thread doesn't
|
|
// start up quickly enough.
|
|
memset(shared_memory_mapping_.memory(), 0, shared_memory_size_);
|
|
memset(client_buffer_.get(), 0, client_buffer_size_bytes_);
|
|
|
|
DCHECK(!audio_output_thread_.get());
|
|
audio_output_thread_ = std::make_unique<base::DelegateSimpleThread>(
|
|
this, "plugin_audio_output_thread");
|
|
audio_output_thread_->Start();
|
|
}
|
|
|
|
void AudioOutputResource::StopThread() {
|
|
// Shut down the socket to escape any hanging |Receive|s.
|
|
if (socket_.get())
|
|
socket_->Shutdown();
|
|
if (audio_output_thread_.get()) {
|
|
audio_output_thread_->Join();
|
|
audio_output_thread_.reset();
|
|
}
|
|
}
|
|
|
|
void AudioOutputResource::Run() {
|
|
// The shared memory represents AudioOutputBufferParameters and the actual
|
|
// data buffer stored as an audio bus.
|
|
media::AudioOutputBuffer* buffer =
|
|
static_cast<media::AudioOutputBuffer*>(shared_memory_mapping_.memory());
|
|
|
|
// This is a constantly increasing counter that is used to verify on the
|
|
// browser side that buffers are in sync.
|
|
uint32_t buffer_index = 0;
|
|
|
|
while (true) {
|
|
int pending_data = 0;
|
|
size_t bytes_read = socket_->Receive(&pending_data, sizeof(pending_data));
|
|
if (bytes_read != sizeof(pending_data)) {
|
|
DCHECK_EQ(bytes_read, 0U);
|
|
break;
|
|
}
|
|
if (pending_data < 0)
|
|
break;
|
|
|
|
{
|
|
base::TimeDelta delay = base::Microseconds(buffer->params.delay_us);
|
|
|
|
audio_output_callback_(client_buffer_.get(), client_buffer_size_bytes_,
|
|
delay.InSecondsF(), user_data_);
|
|
}
|
|
|
|
// Deinterleave the audio data into the shared memory as floats.
|
|
static_assert(kBitsPerAudioOutputSample == 16,
|
|
"FromInterleaved expects 2 bytes.");
|
|
audio_bus_->FromInterleaved<media::SignedInt16SampleTypeTraits>(
|
|
reinterpret_cast<int16_t*>(client_buffer_.get()), audio_bus_->frames());
|
|
|
|
// Inform other side that we have read the data from the shared memory.
|
|
// Let the other end know which buffer we just filled. The buffer index is
|
|
// used to ensure the other end is getting the buffer it expects. For more
|
|
// details on how this works see AudioSyncReader::WaitUntilDataIsReady().
|
|
++buffer_index;
|
|
size_t bytes_sent = socket_->Send(&buffer_index, sizeof(buffer_index));
|
|
if (bytes_sent != sizeof(buffer_index)) {
|
|
DCHECK_EQ(bytes_sent, 0U);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t AudioOutputResource::CommonOpen(
|
|
PP_Resource device_ref,
|
|
PP_Resource config,
|
|
PPB_AudioOutput_Callback audio_output_callback,
|
|
void* user_data,
|
|
scoped_refptr<TrackedCallback> callback) {
|
|
std::string device_id;
|
|
// |device_id| remains empty if |device_ref| is 0, which means the default
|
|
// device.
|
|
if (device_ref != 0) {
|
|
thunk::EnterResourceNoLock<thunk::PPB_DeviceRef_API> enter_device_ref(
|
|
device_ref, true);
|
|
if (enter_device_ref.failed())
|
|
return PP_ERROR_BADRESOURCE;
|
|
device_id = enter_device_ref.object()->GetDeviceRefData().id;
|
|
}
|
|
|
|
if (TrackedCallback::IsPending(open_callback_))
|
|
return PP_ERROR_INPROGRESS;
|
|
if (open_state_ != BEFORE_OPEN)
|
|
return PP_ERROR_FAILED;
|
|
|
|
if (!audio_output_callback)
|
|
return PP_ERROR_BADARGUMENT;
|
|
thunk::EnterResourceNoLock<thunk::PPB_AudioConfig_API> enter_config(config,
|
|
true);
|
|
if (enter_config.failed())
|
|
return PP_ERROR_BADARGUMENT;
|
|
|
|
config_ = config;
|
|
audio_output_callback_ = audio_output_callback;
|
|
user_data_ = user_data;
|
|
open_callback_ = callback;
|
|
bytes_per_second_ = kAudioOutputChannels * (kBitsPerAudioOutputSample / 8) *
|
|
enter_config.object()->GetSampleRate();
|
|
sample_frame_count_ = enter_config.object()->GetSampleFrameCount();
|
|
|
|
PpapiHostMsg_AudioOutput_Open msg(
|
|
device_id, enter_config.object()->GetSampleRate(),
|
|
enter_config.object()->GetSampleFrameCount());
|
|
Call<PpapiPluginMsg_AudioOutput_OpenReply>(
|
|
RENDERER, msg,
|
|
base::BindOnce(&AudioOutputResource::OnPluginMsgOpenReply,
|
|
base::Unretained(this)));
|
|
return PP_OK_COMPLETIONPENDING;
|
|
}
|
|
} // namespace proxy
|
|
} // namespace ppapi
|