diff options
-rw-r--r-- | media/audio/audio_io.h | 6 | ||||
-rw-r--r-- | media/audio/audio_util.cc | 2 | ||||
-rw-r--r-- | media/audio/linux/alsa_output.cc | 212 | ||||
-rw-r--r-- | media/audio/linux/alsa_output.h | 8 | ||||
-rw-r--r-- | media/audio/linux/alsa_output_unittest.cc | 9 |
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, ¶ms); 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 |