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, 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