summaryrefslogtreecommitdiffstats
path: root/media/audio
diff options
context:
space:
mode:
authordalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-07 00:15:47 +0000
committerdalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-07 00:15:47 +0000
commit248eef367dc93ee04204e1896c03b3e0df1a7815 (patch)
treeb1e9bf791f15faddc5bfed06d9ec20582542d465 /media/audio
parentf1958c384a7e4639c76e21d6cb7d2639b69e9a62 (diff)
downloadchromium_src-248eef367dc93ee04204e1896c03b3e0df1a7815.zip
chromium_src-248eef367dc93ee04204e1896c03b3e0df1a7815.tar.gz
chromium_src-248eef367dc93ee04204e1896c03b3e0df1a7815.tar.bz2
Reduce Linux low latency buffer size to 512.
Since ALSA uses an internally large buffer that we fill piecemeal by our internal buffer size, we can reduce our internal size in exchange for more frequent callbacks. This change: - Removes kernel timeslice adjustment. Shouldn't be necessary on modern kernels. - Removes minimum callback time between OnMoreData() calls in favor of locking the shared memory (192kHz audio isn't possible otherwise). - Fixes jitter introduced by premature clipping of delay information. - Removes useless code paths. - Switches to using TimeDelta throughout for accuracy. - Initializes playback with silence to avoid 192kHz startup glitches. This resolves playback issues with 192kHz content and latency issues as noted by our performance tests. With the shared memory block in place we should not see any glitching from the new buffer size. I believe the benefits of a 512-frame buffer w/ blocking shared mem are worth it in comparison to a 2048-frame buffer w/o blocking. BUG=172030 TEST=audio playback is smooth, doesn't glitch, latency tests show ~60ms which is inline with Windows/Mac. Review URL: https://chromiumcodereview.appspot.com/12051082 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@181105 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-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, 103 insertions, 134 deletions
diff --git a/media/audio/audio_io.h b/media/audio/audio_io.h
index e7b9a36..a1978a6 100644
--- a/media/audio/audio_io.h
+++ b/media/audio/audio_io.h
@@ -74,10 +74,8 @@ class MEDIA_EXPORT AudioOutputStream {
// specific.
virtual void OnError(AudioOutputStream* stream, int code) = 0;
- // 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.
+ // Will block until the 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 c03f502..fe7b7ec 100644
--- a/media/audio/audio_util.cc
+++ b/media/audio/audio_util.cc
@@ -200,6 +200,8 @@ 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 1c822b6..ab207ba 100644
--- a/media/audio/linux/alsa_output.cc
+++ b/media/audio/linux/alsa_output.cc
@@ -52,19 +52,6 @@
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)
@@ -155,12 +142,11 @@ 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_(channels_ * params.bits_per_sample() / 8),
+ bytes_per_frame_(params.GetBytesPerFrame()),
packet_size_(params.GetBytesPerBuffer()),
- micros_per_packet_(FramesToMicros(
- params.frames_per_buffer(), sample_rate_)),
- latency_micros_(std::max(AlsaPcmOutputStream::kMinLatencyMicros,
- micros_per_packet_ * 2)),
+ latency_(std::max(
+ base::TimeDelta::FromMicroseconds(kMinLatencyMicros),
+ FramesToTimeDelta(params.frames_per_buffer() * 2, sample_rate_))),
bytes_per_output_frame_(bytes_per_frame_),
alsa_buffer_frames_(0),
stop_stream_(false),
@@ -216,14 +202,14 @@ bool AlsaPcmOutputStream::Open() {
// Try to open the device.
if (requested_device_name_ == kAutoSelectDevice) {
- playback_handle_ = AutoSelectDevice(latency_micros_);
+ playback_handle_ = AutoSelectDevice(latency_.InMicroseconds());
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_micros_);
+ pcm_format_, latency_.InMicroseconds());
}
// Finish initializing the stream if the device was opened successfully.
@@ -291,35 +277,45 @@ void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) {
if (stop_stream_)
return;
- set_source_callback(callback);
-
// 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();
+ if (TransitionTo(kIsPlaying) != kIsPlaying)
+ 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;
- }
- }
+ // 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;
+ }
- if (!stop_stream_)
- WriteTask();
+ 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;
}
+
+ // 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() {
@@ -327,6 +323,7 @@ void AlsaPcmOutputStream::Stop() {
// Reset the callback, so that it is not called anymore.
set_source_callback(NULL);
+ weak_factory_.InvalidateWeakPtrs();
TransitionTo(kIsStopped);
}
@@ -360,22 +357,16 @@ 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.
-
- uint32 buffer_delay = buffer_->forward_bytes() * bytes_per_frame_ /
- bytes_per_output_frame_;
-
- uint32 hardware_delay = GetCurrentDelay() * bytes_per_frame_;
+ const 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(buffer_delay, hardware_delay));
+ audio_bus_.get(), AudioBuffersState(0, 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();
@@ -429,6 +420,9 @@ 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) {
@@ -483,74 +477,47 @@ void AlsaPcmOutputStream::WriteTask() {
void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) {
DCHECK(IsOnAudioThread());
- if (stop_stream_)
+ if (stop_stream_ || state() != kIsPlaying)
return;
const uint32 kTargetFramesAvailable = alsa_buffer_frames_ / 2;
uint32 available_frames = GetAvailableFrames();
- 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 {
- // 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));
+ 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();
+ } 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);
}
-}
-uint32 AlsaPcmOutputStream::FramesToMicros(uint32 frames,
- uint32 sample_rate) {
- return frames * base::Time::kMicrosecondsPerSecond / sample_rate;
+ message_loop_->PostDelayedTask(FROM_HERE, base::Bind(
+ &AlsaPcmOutputStream::WriteTask, weak_factory_.GetWeakPtr()),
+ next_fill_time);
}
-uint32 AlsaPcmOutputStream::FramesToMillis(uint32 frames,
- uint32 sample_rate) {
- return frames * base::Time::kMillisecondsPerSecond / sample_rate;
+// static
+base::TimeDelta AlsaPcmOutputStream::FramesToTimeDelta(int frames,
+ double sample_rate) {
+ return base::TimeDelta::FromMicroseconds(
+ frames * base::Time::kMicrosecondsPerSecond / sample_rate);
}
std::string AlsaPcmOutputStream::FindDeviceForChannels(uint32 channels) {
@@ -623,10 +590,15 @@ 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.
- if (delay < 0 || static_cast<snd_pcm_uframes_t>(delay) > alsa_buffer_frames_)
- delay = 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. 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());
+ }
return delay;
}
@@ -650,7 +622,7 @@ snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() {
<< wrapper_->StrError(available_frames);
return 0;
}
- if (static_cast<uint32>(available_frames) > alsa_buffer_frames_) {
+ if (static_cast<uint32>(available_frames) > alsa_buffer_frames_ * 2) {
LOG(ERROR) << "ALSA returned " << available_frames << " of "
<< alsa_buffer_frames_ << " frames available.";
return alsa_buffer_frames_;
@@ -775,8 +747,10 @@ int AlsaPcmOutputStream::RunDataCallback(AudioBus* audio_bus,
AudioBuffersState buffers_state) {
TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback");
- if (source_callback_)
+ if (source_callback_) {
+ source_callback_->WaitTillDataReady();
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 ffb29f4..1b81046 100644
--- a/media/audio/linux/alsa_output.h
+++ b/media/audio/linux/alsa_output.h
@@ -125,8 +125,7 @@ class MEDIA_EXPORT AlsaPcmOutputStream : public AudioOutputStream {
void ScheduleNextWrite(bool source_exhausted);
// Utility functions for talking with the ALSA API.
- static uint32 FramesToMicros(uint32 frames, uint32 sample_rate);
- static uint32 FramesToMillis(uint32 frames, uint32 sample_rate);
+ static base::TimeDelta FramesToTimeDelta(int frames, double sample_rate);
std::string FindDeviceForChannels(uint32 channels);
snd_pcm_sframes_t GetAvailableFrames();
snd_pcm_sframes_t GetCurrentDelay();
@@ -173,8 +172,7 @@ class MEDIA_EXPORT AlsaPcmOutputStream : public AudioOutputStream {
// Device configuration data. Populated after OpenTask() completes.
std::string device_name_;
uint32 packet_size_;
- uint32 micros_per_packet_;
- uint32 latency_micros_;
+ base::TimeDelta latency_;
uint32 bytes_per_output_frame_;
uint32 alsa_buffer_frames_;
@@ -210,8 +208,6 @@ 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 1db97af..f2df3f1 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 = 2 * AlsaPcmOutputStream::FramesToMicros(
- kOverMinLatencyPacketSize, kTestSampleRate);
+ int64 expected_micros = AlsaPcmOutputStream::FramesToTimeDelta(
+ kOverMinLatencyPacketSize * 2, kTestSampleRate).InMicroseconds();
EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
.WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
@@ -315,9 +315,8 @@ TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
}
TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
- int64 expected_micros = 2 *
- AlsaPcmOutputStream::FramesToMicros(kTestPacketSize / kTestBytesPerFrame,
- kTestSampleRate);
+ int64 expected_micros = AlsaPcmOutputStream::FramesToTimeDelta(
+ 2 * kTestFramesPerPacket, kTestSampleRate).InMicroseconds();
// Open() call opens the playback device, sets the parameters, posts a task
// with the resulting configuration data, and transitions the object state to