blob: 906d7367daf321c837eb54d89d16fb5420f0e526 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/client/audio/audio_player_buffer.h"
#include <algorithm>
#include <string>
#include "base/logging.h"
#include "base/stl_util.h"
namespace {
// If queue grows bigger than this we start dropping packets.
const int kMaxQueueLatencyMs = 150;
const int kFrameSizeMs = 10;
const int kMaxQueueLatencyFrames = kMaxQueueLatencyMs / kFrameSizeMs;
}; // namespace
namespace remoting {
struct AudioPlayerBuffer::AudioFrameRequest {
const size_t bytes_needed_;
const void* samples_;
base::Closure callback_;
size_t bytes_extracted_;
AudioFrameRequest(const size_t bytes_needed,
const void* samples,
const base::Closure& callback);
~AudioFrameRequest() = default;
};
AudioPlayerBuffer::AudioFrameRequest::AudioFrameRequest(
const size_t bytes_needed,
const void* samples,
const base::Closure& callback)
: bytes_needed_(bytes_needed),
samples_(samples),
callback_(callback),
bytes_extracted_(0) {}
AudioPlayerBuffer::AudioPlayerBuffer()
: queued_bytes_(0), bytes_consumed_(0), weak_factory_(this) {
DETACH_FROM_THREAD(thread_checker_);
}
AudioPlayerBuffer::~AudioPlayerBuffer() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ResetQueue();
}
void AudioPlayerBuffer::AddAudioPacket(std::unique_ptr<AudioPacket> packet) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK_EQ(1, packet->data_size());
DCHECK_EQ(AudioPacket::ENCODING_RAW, packet->encoding());
DCHECK_NE(AudioPacket::SAMPLING_RATE_INVALID, packet->sampling_rate());
DCHECK_EQ(buffered_sampling_rate(), packet->sampling_rate());
DCHECK_EQ(buffered_byes_per_sample(),
static_cast<int>(packet->bytes_per_sample()));
DCHECK_EQ(buffered_channels(), static_cast<int>(packet->channels()));
DCHECK_EQ(packet->data(0).size() %
(buffered_channels() * buffered_byes_per_sample()),
0u);
// Push the new data to the back of the queue.
queued_bytes_ += packet->data(0).size();
queued_packets_.push_back(std::move(packet));
// TODO(nicholss): MIGHT WANT TO JUST CALCULATE THIS ONCE.
int max_buffer_size_ = kMaxQueueLatencyFrames * bytes_per_frame();
// Trim off the front of the buffer if we have enqueued too many packets.
while (queued_bytes_ > max_buffer_size_) {
queued_bytes_ -= queued_packets_.front()->data(0).size() - bytes_consumed_;
DCHECK_GE(queued_bytes_, 0);
queued_packets_.pop_front();
bytes_consumed_ = 0;
}
// Attempt to process a FrameRequest now we have changed queued packets.
ProcessFrameRequestQueue();
}
void AudioPlayerBuffer::Stop() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ResetQueue();
}
void AudioPlayerBuffer::AsyncGetAudioFrame(uint32_t buffer_size,
void* buffer,
const base::Closure& callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Create an AudioFrameRequest and enqueue it into the buffer.
CHECK_EQ(buffer_size % bytes_per_frame(), 0u);
std::unique_ptr<AudioFrameRequest> audioFrameRequest(
new AudioFrameRequest(buffer_size, buffer, callback));
queued_requests_.push_back(std::move(audioFrameRequest));
ProcessFrameRequestQueue();
return;
}
void AudioPlayerBuffer::ResetQueue() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
queued_packets_.clear();
queued_bytes_ = 0;
bytes_consumed_ = 0;
queued_requests_.clear();
}
uint32_t AudioPlayerBuffer::GetAudioFrame(uint32_t buffer_size, void* buffer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
const size_t bytes_needed = bytes_per_frame();
// Make sure we don't overrun the buffer.
CHECK_EQ(buffer_size, bytes_needed);
char* next_frame = static_cast<char*>(buffer);
size_t bytes_extracted = 0;
while (bytes_extracted < bytes_needed) {
// Check if we've run out of samples for this packet.
if (queued_packets_.empty()) {
memset(next_frame, 0, bytes_needed - bytes_extracted);
return 0;
}
// Pop off the packet if we've already consumed all its bytes.
if (queued_packets_.front()->data(0).size() == bytes_consumed_) {
queued_packets_.pop_front();
bytes_consumed_ = 0;
continue;
}
const std::string& packet_data = queued_packets_.front()->data(0);
size_t bytes_to_copy = std::min(packet_data.size() - bytes_consumed_,
bytes_needed - bytes_extracted);
memcpy(next_frame, packet_data.data() + bytes_consumed_, bytes_to_copy);
next_frame += bytes_to_copy;
bytes_consumed_ += bytes_to_copy;
bytes_extracted += bytes_to_copy;
queued_bytes_ -= bytes_to_copy;
DCHECK_GE(queued_bytes_, 0);
}
return bytes_extracted;
}
// This is called when the Frame Request Queue or the Sample Queue is changed.
void AudioPlayerBuffer::ProcessFrameRequestQueue() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Get the active request if there is one.
while (!queued_requests_.empty() && !queued_packets_.empty()) {
AudioFrameRequest* activeRequest = queued_requests_.front().get();
// Copy any available data into the active request up to as much requested
while (activeRequest->bytes_extracted_ < activeRequest->bytes_needed_ &&
!queued_packets_.empty()) {
char* next_frame = (char*)(activeRequest->samples_);
const std::string& packet_data = queued_packets_.front()->data(0);
size_t bytes_to_copy = std::min(
packet_data.size() - bytes_consumed_,
activeRequest->bytes_needed_ - activeRequest->bytes_extracted_);
memcpy(next_frame, packet_data.data() + bytes_consumed_, bytes_to_copy);
bytes_consumed_ += bytes_to_copy;
activeRequest->bytes_extracted_ += bytes_to_copy;
queued_bytes_ -= bytes_to_copy;
DCHECK_GE(queued_bytes_, 0);
// Pop off the packet if we've already consumed all its bytes.
if (queued_packets_.front()->data(0).size() == bytes_consumed_) {
queued_packets_.pop_front();
bytes_consumed_ = 0;
}
}
// If this request is fulfilled, call the callback and pop it off the queue.
if (activeRequest->bytes_extracted_ == activeRequest->bytes_needed_) {
activeRequest->callback_.Run();
queued_requests_.pop_front();
activeRequest = nullptr;
}
}
}
uint32_t AudioPlayerBuffer::samples_per_frame() const {
return (buffered_sampling_rate() * kFrameSizeMs) /
base::Time::kMillisecondsPerSecond;
}
AudioPacket::SamplingRate AudioPlayerBuffer::buffered_sampling_rate() const {
return AudioPacket::SAMPLING_RATE_48000;
}
AudioPacket::Channels AudioPlayerBuffer::buffered_channels() const {
return AudioPacket::CHANNELS_STEREO;
}
AudioPacket::BytesPerSample AudioPlayerBuffer::buffered_byes_per_sample()
const {
return AudioPacket::BYTES_PER_SAMPLE_2;
}
uint32_t AudioPlayerBuffer::bytes_per_frame() const {
return buffered_channels() * buffered_byes_per_sample() * samples_per_frame();
}
base::WeakPtr<AudioStreamConsumer>
AudioPlayerBuffer::AudioStreamConsumerAsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace remoting