summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/audio/audio_io.h6
-rw-r--r--media/audio/audio_util.cc2
-rw-r--r--media/audio/linux/alsa_output.cc212
-rw-r--r--media/audio/linux/alsa_output.h8
-rw-r--r--media/audio/linux/alsa_output_unittest.cc9
5 files changed, 134 insertions, 103 deletions
diff --git a/media/audio/audio_io.h b/media/audio/audio_io.h
index a1978a6..e7b9a36 100644
--- a/media/audio/audio_io.h
+++ b/media/audio/audio_io.h
@@ -74,8 +74,10 @@ class MEDIA_EXPORT AudioOutputStream {
// specific.
virtual void OnError(AudioOutputStream* stream, int code) = 0;
- // Will block until the client has written its audio data or 1.5 seconds
- // have elapsed.
+ // Deprecated. DO NOT USE. Waits until data becomes available. Used only
+ // by Windows' WaveOut clients which may be extremely laggy. Will yield the
+ // current thread until the renderer client has written its audio data or
+ // 1.5 seconds have elapsed.
virtual void WaitTillDataReady() {}
protected:
diff --git a/media/audio/audio_util.cc b/media/audio/audio_util.cc
index fe7b7ec..c03f502 100644
--- a/media/audio/audio_util.cc
+++ b/media/audio/audio_util.cc
@@ -200,8 +200,6 @@ size_t GetAudioHardwareBufferSize() {
HRESULT hr = CoreAudioUtil::GetPreferredAudioParameters(eRender, eConsole,
&params);
return FAILED(hr) ? kFallbackBufferSize : params.frames_per_buffer();
-#elif defined(OS_LINUX)
- return 512;
#else
return 2048;
#endif
diff --git a/media/audio/linux/alsa_output.cc b/media/audio/linux/alsa_output.cc
index ab207ba..1c822b6 100644
--- a/media/audio/linux/alsa_output.cc
+++ b/media/audio/linux/alsa_output.cc
@@ -52,6 +52,19 @@
namespace media {
+// Amount of time to wait if we've exhausted the data source. This is to avoid
+// busy looping.
+static const uint32 kNoDataSleepMilliseconds = 10;
+
+// Mininum interval between OnMoreData() calls. This is to avoid glitches for
+// WebAudio which needs time to generate new data.
+static const uint32 kMinIntervalBetweenOnMoreDataCallsInMs = 5;
+
+// According to the linux nanosleep manpage, nanosleep on linux can miss the
+// deadline by up to 10ms because the kernel timeslice is 10ms. This should be
+// enough to compensate for the timeslice, and any additional slowdowns.
+static const uint32 kSleepErrorMilliseconds = 10;
+
// Set to 0 during debugging if you want error messages due to underrun
// events or other recoverable errors.
#if defined(NDEBUG)
@@ -142,11 +155,12 @@ AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name,
channel_layout_(params.channel_layout()),
sample_rate_(params.sample_rate()),
bytes_per_sample_(params.bits_per_sample() / 8),
- bytes_per_frame_(params.GetBytesPerFrame()),
+ bytes_per_frame_(channels_ * params.bits_per_sample() / 8),
packet_size_(params.GetBytesPerBuffer()),
- latency_(std::max(
- base::TimeDelta::FromMicroseconds(kMinLatencyMicros),
- FramesToTimeDelta(params.frames_per_buffer() * 2, sample_rate_))),
+ micros_per_packet_(FramesToMicros(
+ params.frames_per_buffer(), sample_rate_)),
+ latency_micros_(std::max(AlsaPcmOutputStream::kMinLatencyMicros,
+ micros_per_packet_ * 2)),
bytes_per_output_frame_(bytes_per_frame_),
alsa_buffer_frames_(0),
stop_stream_(false),
@@ -202,14 +216,14 @@ bool AlsaPcmOutputStream::Open() {
// Try to open the device.
if (requested_device_name_ == kAutoSelectDevice) {
- playback_handle_ = AutoSelectDevice(latency_.InMicroseconds());
+ playback_handle_ = AutoSelectDevice(latency_micros_);
if (playback_handle_)
DVLOG(1) << "Auto-selected device: " << device_name_;
} else {
device_name_ = requested_device_name_;
playback_handle_ = alsa_util::OpenPlaybackDevice(
wrapper_, device_name_.c_str(), channels_, sample_rate_,
- pcm_format_, latency_.InMicroseconds());
+ pcm_format_, latency_micros_);
}
// Finish initializing the stream if the device was opened successfully.
@@ -277,45 +291,35 @@ void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) {
if (stop_stream_)
return;
- // Only post the task if we can enter the playing state.
- if (TransitionTo(kIsPlaying) != kIsPlaying)
- return;
+ set_source_callback(callback);
- // Before starting, the buffer might have audio from previous user of this
- // device.
- buffer_->Clear();
+ // Only post the task if we can enter the playing state.
+ if (TransitionTo(kIsPlaying) == kIsPlaying) {
+ // Before starting, the buffer might have audio from previous user of this
+ // device.
+ buffer_->Clear();
- // When starting again, drop all packets in the device and prepare it again
- // in case we are restarting from a pause state and need to flush old data.
- int error = wrapper_->PcmDrop(playback_handle_);
- if (error < 0 && error != -EAGAIN) {
- LOG(ERROR) << "Failure clearing playback device ("
- << wrapper_->PcmName(playback_handle_) << "): "
- << wrapper_->StrError(error);
- stop_stream_ = true;
- return;
- }
+ // When starting again, drop all packets in the device and prepare it again
+ // in case we are restarting from a pause state and need to flush old data.
+ int error = wrapper_->PcmDrop(playback_handle_);
+ if (error < 0 && error != -EAGAIN) {
+ LOG(ERROR) << "Failure clearing playback device ("
+ << wrapper_->PcmName(playback_handle_) << "): "
+ << wrapper_->StrError(error);
+ stop_stream_ = true;
+ } else {
+ error = wrapper_->PcmPrepare(playback_handle_);
+ if (error < 0 && error != -EAGAIN) {
+ LOG(ERROR) << "Failure preparing stream ("
+ << wrapper_->PcmName(playback_handle_) << "): "
+ << wrapper_->StrError(error);
+ stop_stream_ = true;
+ }
+ }
- error = wrapper_->PcmPrepare(playback_handle_);
- if (error < 0 && error != -EAGAIN) {
- LOG(ERROR) << "Failure preparing stream ("
- << wrapper_->PcmName(playback_handle_) << "): "
- << wrapper_->StrError(error);
- stop_stream_ = true;
- return;
+ if (!stop_stream_)
+ WriteTask();
}
-
- // Ensure the first buffer is silence to avoid startup glitches.
- int buffer_size = GetAvailableFrames() * bytes_per_output_frame_;
- scoped_refptr<DataBuffer> silent_packet = new DataBuffer(buffer_size);
- silent_packet->SetDataSize(buffer_size);
- memset(silent_packet->GetWritableData(), 0, silent_packet->GetDataSize());
- buffer_->Append(silent_packet);
- WritePacket();
-
- // Start the callback chain.
- set_source_callback(callback);
- WriteTask();
}
void AlsaPcmOutputStream::Stop() {
@@ -323,7 +327,6 @@ void AlsaPcmOutputStream::Stop() {
// Reset the callback, so that it is not called anymore.
set_source_callback(NULL);
- weak_factory_.InvalidateWeakPtrs();
TransitionTo(kIsStopped);
}
@@ -357,16 +360,22 @@ void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) {
if (!buffer_->forward_bytes()) {
// Before making a request to source for data we need to determine the
// delay (in bytes) for the requested data to be played.
- const uint32 hardware_delay = GetCurrentDelay() * bytes_per_frame_;
+
+ uint32 buffer_delay = buffer_->forward_bytes() * bytes_per_frame_ /
+ bytes_per_output_frame_;
+
+ uint32 hardware_delay = GetCurrentDelay() * bytes_per_frame_;
scoped_refptr<media::DataBuffer> packet =
new media::DataBuffer(packet_size_);
int frames_filled = RunDataCallback(
- audio_bus_.get(), AudioBuffersState(0, hardware_delay));
-
+ audio_bus_.get(), AudioBuffersState(buffer_delay, hardware_delay));
size_t packet_size = frames_filled * bytes_per_frame_;
DCHECK_LE(packet_size, packet_size_);
+ // Reset the |last_fill_time| to avoid back to back RunDataCallback().
+ last_fill_time_ = base::Time::Now();
+
// TODO(dalecurtis): Channel downmixing, upmixing, should be done in mixer;
// volume adjust should use SSE optimized vector_fmul() prior to interleave.
AudioBus* output_bus = audio_bus_.get();
@@ -420,9 +429,6 @@ void AlsaPcmOutputStream::WritePacket() {
static_cast<snd_pcm_sframes_t>(buffer_size / bytes_per_output_frame_),
GetAvailableFrames());
- if (!frames)
- return;
-
snd_pcm_sframes_t frames_written =
wrapper_->PcmWritei(playback_handle_, buffer_data, frames);
if (frames_written < 0) {
@@ -477,47 +483,74 @@ void AlsaPcmOutputStream::WriteTask() {
void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) {
DCHECK(IsOnAudioThread());
- if (stop_stream_ || state() != kIsPlaying)
+ if (stop_stream_)
return;
const uint32 kTargetFramesAvailable = alsa_buffer_frames_ / 2;
uint32 available_frames = GetAvailableFrames();
-
- base::TimeDelta next_fill_time;
- if (buffer_->forward_bytes() && available_frames) {
- // If we've got data available and ALSA has room, deliver it immediately.
- next_fill_time = base::TimeDelta();
- } else if (buffer_->forward_bytes()) {
- // If we've got data available and no room, poll until room is available.
- // Polling in this manner allows us to ensure a more consistent callback
- // schedule. In testing this yields a variance of +/- 5ms versus the non-
- // polling strategy which is around +/- 30ms and bimodal.
- next_fill_time = base::TimeDelta::FromMilliseconds(5);
- } else if (available_frames < kTargetFramesAvailable) {
- // Schedule the next write for the moment when the available buffer of the
- // sound card hits |kTargetFramesAvailable|.
- next_fill_time = FramesToTimeDelta(
- kTargetFramesAvailable - available_frames, sample_rate_);
- } else if (!source_exhausted) {
- // The sound card has |kTargetFramesAvailable| or more frames available.
- // Invoke the next write immediately to avoid underrun.
- next_fill_time = base::TimeDelta();
+ uint32 frames_in_buffer = buffer_->forward_bytes() / bytes_per_output_frame_;
+
+ // Next write is initially scheduled for the moment when half of a packet
+ // has been played out.
+ uint32 next_fill_time_ms =
+ FramesToMillis(frames_per_packet_ / 2, sample_rate_);
+
+ if (frames_in_buffer && available_frames) {
+ // There is data in the current buffer, consume them immediately once we
+ // have enough space in the soundcard.
+ if (frames_in_buffer <= available_frames)
+ next_fill_time_ms = 0;
} else {
- // The sound card has frames available, but our source is exhausted, so
- // avoid busy looping by delaying a bit.
- next_fill_time = base::TimeDelta::FromMilliseconds(10);
+ // Otherwise schedule the next write for the moment when the available
+ // buffer of the soundcards hits the |kTargetFramesAvailable|.
+ if (available_frames < kTargetFramesAvailable) {
+ uint32 frames_until_empty_enough =
+ kTargetFramesAvailable - available_frames;
+ next_fill_time_ms =
+ FramesToMillis(frames_until_empty_enough, sample_rate_);
+
+ // Adjust for the kernel timeslice and any additional slowdown.
+ // TODO(xians): Remove this adjustment if it is not required by
+ // low performance machines any more.
+ if (next_fill_time_ms > kSleepErrorMilliseconds)
+ next_fill_time_ms -= kSleepErrorMilliseconds;
+ else
+ next_fill_time_ms = 0;
+ } else {
+ // The sound card has |kTargetFramesAvailable| or more frames available.
+ // Invoke the next write immediately to avoid underrun.
+ next_fill_time_ms = 0;
+ }
+
+ // Avoid back-to-back writing.
+ base::TimeDelta delay = base::Time::Now() - last_fill_time_;
+ if (delay.InMilliseconds() < kMinIntervalBetweenOnMoreDataCallsInMs &&
+ next_fill_time_ms < kMinIntervalBetweenOnMoreDataCallsInMs)
+ next_fill_time_ms = kMinIntervalBetweenOnMoreDataCallsInMs;
+ }
+
+ // Avoid busy looping if the data source is exhausted.
+ if (source_exhausted)
+ next_fill_time_ms = std::max(next_fill_time_ms, kNoDataSleepMilliseconds);
+
+ // Only schedule more reads/writes if we are still in the playing state.
+ if (state() == kIsPlaying) {
+ message_loop_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&AlsaPcmOutputStream::WriteTask,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(next_fill_time_ms));
}
+}
- message_loop_->PostDelayedTask(FROM_HERE, base::Bind(
- &AlsaPcmOutputStream::WriteTask, weak_factory_.GetWeakPtr()),
- next_fill_time);
+uint32 AlsaPcmOutputStream::FramesToMicros(uint32 frames,
+ uint32 sample_rate) {
+ return frames * base::Time::kMicrosecondsPerSecond / sample_rate;
}
-// static
-base::TimeDelta AlsaPcmOutputStream::FramesToTimeDelta(int frames,
- double sample_rate) {
- return base::TimeDelta::FromMicroseconds(
- frames * base::Time::kMicrosecondsPerSecond / sample_rate);
+uint32 AlsaPcmOutputStream::FramesToMillis(uint32 frames,
+ uint32 sample_rate) {
+ return frames * base::Time::kMillisecondsPerSecond / sample_rate;
}
std::string AlsaPcmOutputStream::FindDeviceForChannels(uint32 channels) {
@@ -590,15 +623,10 @@ snd_pcm_sframes_t AlsaPcmOutputStream::GetCurrentDelay() {
}
}
- // snd_pcm_delay() sometimes returns crazy values. In this case return delay
- // of data we know currently is in ALSA's buffer. Note: When the underlying
- // driver is PulseAudio based, certain configuration settings (e.g., tsched=1)
- // will generate much larger delay values than |alsa_buffer_frames_|, so only
- // clip if delay is truly crazy (> 10x expected).
- if (delay < 0 ||
- static_cast<snd_pcm_uframes_t>(delay) > alsa_buffer_frames_ * 10) {
- delay = std::max(0L, alsa_buffer_frames_ - GetAvailableFrames());
- }
+ // snd_pcm_delay() sometimes returns crazy values. In this case return delay
+ // of data we know currently is in ALSA's buffer.
+ if (delay < 0 || static_cast<snd_pcm_uframes_t>(delay) > alsa_buffer_frames_)
+ delay = alsa_buffer_frames_ - GetAvailableFrames();
return delay;
}
@@ -622,7 +650,7 @@ snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() {
<< wrapper_->StrError(available_frames);
return 0;
}
- if (static_cast<uint32>(available_frames) > alsa_buffer_frames_ * 2) {
+ if (static_cast<uint32>(available_frames) > alsa_buffer_frames_) {
LOG(ERROR) << "ALSA returned " << available_frames << " of "
<< alsa_buffer_frames_ << " frames available.";
return alsa_buffer_frames_;
@@ -747,10 +775,8 @@ int AlsaPcmOutputStream::RunDataCallback(AudioBus* audio_bus,
AudioBuffersState buffers_state) {
TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback");
- if (source_callback_) {
- source_callback_->WaitTillDataReady();
+ if (source_callback_)
return source_callback_->OnMoreData(audio_bus, buffers_state);
- }
return 0;
}
diff --git a/media/audio/linux/alsa_output.h b/media/audio/linux/alsa_output.h
index 1b81046..ffb29f4 100644
--- a/media/audio/linux/alsa_output.h
+++ b/media/audio/linux/alsa_output.h
@@ -125,7 +125,8 @@ class MEDIA_EXPORT AlsaPcmOutputStream : public AudioOutputStream {
void ScheduleNextWrite(bool source_exhausted);
// Utility functions for talking with the ALSA API.
- static base::TimeDelta FramesToTimeDelta(int frames, double sample_rate);
+ static uint32 FramesToMicros(uint32 frames, uint32 sample_rate);
+ static uint32 FramesToMillis(uint32 frames, uint32 sample_rate);
std::string FindDeviceForChannels(uint32 channels);
snd_pcm_sframes_t GetAvailableFrames();
snd_pcm_sframes_t GetCurrentDelay();
@@ -172,7 +173,8 @@ class MEDIA_EXPORT AlsaPcmOutputStream : public AudioOutputStream {
// Device configuration data. Populated after OpenTask() completes.
std::string device_name_;
uint32 packet_size_;
- base::TimeDelta latency_;
+ uint32 micros_per_packet_;
+ uint32 latency_micros_;
uint32 bytes_per_output_frame_;
uint32 alsa_buffer_frames_;
@@ -208,6 +210,8 @@ class MEDIA_EXPORT AlsaPcmOutputStream : public AudioOutputStream {
AudioSourceCallback* source_callback_;
+ base::Time last_fill_time_; // Time for the last OnMoreData() callback.
+
// Container for retrieving data from AudioSourceCallback::OnMoreData().
scoped_ptr<AudioBus> audio_bus_;
diff --git a/media/audio/linux/alsa_output_unittest.cc b/media/audio/linux/alsa_output_unittest.cc
index f2df3f1..1db97af 100644
--- a/media/audio/linux/alsa_output_unittest.cc
+++ b/media/audio/linux/alsa_output_unittest.cc
@@ -286,8 +286,8 @@ TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
// Test that having more packets ends up with a latency based on packet size.
const int kOverMinLatencyPacketSize = kPacketFramesInMinLatency + 1;
- int64 expected_micros = AlsaPcmOutputStream::FramesToTimeDelta(
- kOverMinLatencyPacketSize * 2, kTestSampleRate).InMicroseconds();
+ int64 expected_micros = 2 * AlsaPcmOutputStream::FramesToMicros(
+ kOverMinLatencyPacketSize, kTestSampleRate);
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
@@ -315,8 +315,9 @@ TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
}
TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
- int64 expected_micros = AlsaPcmOutputStream::FramesToTimeDelta(
- 2 * kTestFramesPerPacket, kTestSampleRate).InMicroseconds();
+ int64 expected_micros = 2 *
+ AlsaPcmOutputStream::FramesToMicros(kTestPacketSize / kTestBytesPerFrame,
+ kTestSampleRate);
// Open() call opens the playback device, sets the parameters, posts a task
// with the resulting configuration data, and transitions the object state to