Index: media/audio/linux/alsa_output.cc |
diff --git a/media/audio/linux/alsa_output.cc b/media/audio/linux/alsa_output.cc |
index 8212413507665d3bba736ba96a28dd41f51f45dc..3ce66446f98248bdc7fe0a3c458397b5c4d7fad4 100644 |
--- a/media/audio/linux/alsa_output.cc |
+++ b/media/audio/linux/alsa_output.cc |
@@ -84,6 +84,8 @@ |
#include "media/audio/audio_util.h" |
#include "media/audio/linux/alsa_wrapper.h" |
#include "media/audio/linux/audio_manager_linux.h" |
+#include "media/base/data_buffer.h" |
+#include "media/base/seekable_buffer.h" |
// Amount of time to wait if we've exhausted the data source. This is to avoid |
// busy looping. |
@@ -376,7 +378,8 @@ void AlsaPcmOutputStream::OpenTask(uint32 packet_size) { |
DCHECK_EQ(MessageLoop::current(), message_loop_); |
// Initialize the configuration variables. |
- frames_per_packet_ = packet_size / bytes_per_frame_; |
+ packet_size_ = packet_size; |
+ frames_per_packet_ = packet_size_ / bytes_per_frame_; |
// Try to open the device. |
micros_per_packet_ = |
@@ -397,9 +400,22 @@ void AlsaPcmOutputStream::OpenTask(uint32 packet_size) { |
if (playback_handle_ == NULL) { |
stop_stream_ = true; |
} else { |
- packet_.reset(new Packet(packet_size)); |
- if (should_downmix_) { |
- bytes_per_output_frame_ = 2 * bytes_per_sample_; |
+ bytes_per_output_frame_ = should_downmix_ ? 2 * bytes_per_sample_ : |
+ bytes_per_frame_; |
+ uint32 output_packet_size = frames_per_packet_ * bytes_per_output_frame_; |
+ buffer_.reset(new media::SeekableBuffer(0, output_packet_size)); |
+ |
+ // Get alsa buffer size. |
+ snd_pcm_uframes_t buffer_size; |
+ snd_pcm_uframes_t period_size; |
+ int error = wrapper_->PcmGetParams(playback_handle_, &buffer_size, |
+ &period_size); |
+ if (error < 0) { |
+ LOG(ERROR) << "Failed to get playback buffer size from ALSA: " |
+ << wrapper_->StrError(error); |
+ alsa_buffer_frames_ = frames_per_packet_; |
+ } else { |
+ alsa_buffer_frames_ = buffer_size; |
} |
} |
} |
@@ -431,17 +447,7 @@ void AlsaPcmOutputStream::StartTask() { |
return; |
} |
- // Do a best-effort pre-roll to fill the buffer. Use integer rounding to find |
- // the maximum number of full packets that can fit into the buffer. |
- // |
- // TODO(ajwong): Handle EAGAIN. |
- const uint32 num_preroll = latency_micros_ / micros_per_packet_; |
- for (uint32 i = 0; i < num_preroll; ++i) { |
- BufferPacket(packet_.get()); |
- WritePacket(packet_.get()); |
- } |
- |
- ScheduleNextWrite(packet_.get()); |
+ ScheduleNextWrite(); |
} |
void AlsaPcmOutputStream::CloseTask() { |
@@ -456,67 +462,51 @@ void AlsaPcmOutputStream::CloseTask() { |
playback_handle_ = NULL; |
// Release the buffer. |
- packet_.reset(); |
+ buffer_.reset(); |
// Signal anything that might already be scheduled to stop. |
stop_stream_ = true; |
} |
-void AlsaPcmOutputStream::BufferPacket(Packet* packet) { |
+void AlsaPcmOutputStream::BufferPacket() { |
DCHECK_EQ(MessageLoop::current(), message_loop_); |
// If stopped, simulate a 0-lengthed packet. |
if (stop_stream_) { |
- packet->used = packet->size = 0; |
+ buffer_->Clear(); |
return; |
} |
- // Request more data if we don't have any cached. |
- if (packet->used >= packet->size) { |
+ // Request more data if we have capacity. |
+ if (buffer_->forward_capacity() > 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. |
- snd_pcm_sframes_t delay = 0; |
- |
- // Don't query ALSA's delay if we have underrun since it'll be jammed at |
- // some non-zero value and potentially even negative! |
- if (wrapper_->PcmState(playback_handle_) != SND_PCM_STATE_XRUN) { |
- int error = wrapper_->PcmDelay(playback_handle_, &delay); |
- if (error >= 0) { |
- // Convert frames to bytes, but watch out for those negatives! |
- delay = (delay < 0 ? 0 : delay) * bytes_per_output_frame_; |
- } else { |
- // Assume a delay of zero and attempt to recover the device. |
- delay = 0; |
- error = wrapper_->PcmRecover(playback_handle_, |
- error, |
- kPcmRecoverIsSilent); |
- if (error < 0) { |
- LOG(ERROR) << "Failed querying delay: " << wrapper_->StrError(error); |
- } |
- } |
- } |
+ snd_pcm_sframes_t delay = buffer_->forward_bytes() * bytes_per_frame_ / |
+ bytes_per_output_frame_ + GetCurrentDelay() * bytes_per_output_frame_; |
- packet->used = 0; |
- packet->size = shared_data_.OnMoreData(this, packet->buffer.get(), |
- packet->capacity, delay); |
- CHECK(packet->size <= packet->capacity) << "Data source overran buffer."; |
+ media::DataBuffer* packet = new media::DataBuffer(packet_size_); |
+ size_t packet_size = |
+ shared_data_.OnMoreData(this, packet->GetWritableData(), |
+ packet->GetBufferSize(), delay); |
+ CHECK(packet_size <= packet->GetBufferSize()) << |
+ "Data source overran buffer."; |
// This should not happen, but incase it does, drop any trailing bytes |
// that aren't large enough to make a frame. Without this, packet writing |
// may stall because the last few bytes in the packet may never get used by |
// WritePacket. |
- DCHECK(packet->size % bytes_per_frame_ == 0); |
- packet->size = (packet->size / bytes_per_frame_) * bytes_per_frame_; |
+ DCHECK(packet_size % bytes_per_frame_ == 0); |
+ packet_size = (packet_size / bytes_per_frame_) * bytes_per_frame_; |
if (should_downmix_) { |
- if (media::FoldChannels(packet->buffer.get(), |
- packet->size, |
+ if (media::FoldChannels(packet->GetWritableData(), |
+ packet_size, |
channels_, |
bytes_per_sample_, |
shared_data_.volume())) { |
// Adjust packet size for downmix. |
- packet->size = |
- packet->size / bytes_per_frame_ * bytes_per_output_frame_; |
+ packet_size = |
+ packet_size / bytes_per_frame_ * bytes_per_output_frame_; |
} else { |
LOG(ERROR) << "Folding failed"; |
} |
@@ -526,59 +516,60 @@ void AlsaPcmOutputStream::BufferPacket(Packet* packet) { |
// Handle channel order for 5.0 audio. |
if (channels_ == 5) { |
if (bytes_per_sample_ == 1) { |
- Swizzle50Layout(reinterpret_cast<uint8*>(packet->buffer.get()), |
- packet->size); |
+ Swizzle50Layout(packet->GetWritableData(), packet_size); |
} else if (bytes_per_sample_ == 2) { |
- Swizzle50Layout(reinterpret_cast<int16*>(packet->buffer.get()), |
- packet->size); |
+ Swizzle50Layout(packet->GetWritableData(), packet_size); |
} else if (bytes_per_sample_ == 4) { |
- Swizzle50Layout(reinterpret_cast<int32*>(packet->buffer.get()), |
- packet->size); |
+ Swizzle50Layout(packet->GetWritableData(), packet_size); |
} |
} |
// Handle channel order for 5.1 audio. |
if (channels_ == 6) { |
if (bytes_per_sample_ == 1) { |
- Swizzle51Layout(reinterpret_cast<uint8*>(packet->buffer.get()), |
- packet->size); |
+ Swizzle51Layout(packet->GetWritableData(), packet_size); |
} else if (bytes_per_sample_ == 2) { |
- Swizzle51Layout(reinterpret_cast<int16*>(packet->buffer.get()), |
- packet->size); |
+ Swizzle51Layout(packet->GetWritableData(), packet_size); |
} else if (bytes_per_sample_ == 4) { |
- Swizzle51Layout(reinterpret_cast<int32*>(packet->buffer.get()), |
- packet->size); |
+ Swizzle51Layout(packet->GetWritableData(), packet_size); |
} |
} |
- media::AdjustVolume(packet->buffer.get(), |
- packet->size, |
+ media::AdjustVolume(packet->GetWritableData(), |
+ packet_size, |
channels_, |
bytes_per_sample_, |
shared_data_.volume()); |
+ |
+ packet->SetDataSize(packet_size); |
+ |
+ // Add the packet to the buffer. |
+ buffer_->Append(packet); |
} |
} |
} |
-void AlsaPcmOutputStream::WritePacket(Packet* packet) { |
+void AlsaPcmOutputStream::WritePacket() { |
DCHECK_EQ(MessageLoop::current(), message_loop_); |
- CHECK(packet->size % bytes_per_output_frame_ == 0); |
- |
// If the device is in error, just eat the bytes. |
if (stop_stream_) { |
- packet->used = packet->size; |
+ buffer_->Clear(); |
return; |
} |
- if (packet->used < packet->size) { |
- char* buffer_pos = packet->buffer.get() + packet->used; |
- snd_pcm_sframes_t frames = FramesInPacket(*packet, bytes_per_output_frame_); |
+ CHECK_EQ(buffer_->forward_bytes() % bytes_per_output_frame_, 0u); |
+ |
+ const uint8* buffer_data; |
+ size_t buffer_size; |
+ if (buffer_->GetCurrentChunk(&buffer_data, &buffer_size)) { |
+ buffer_size = buffer_size - (buffer_size % bytes_per_output_frame_); |
+ snd_pcm_sframes_t frames = buffer_size / bytes_per_output_frame_; |
DCHECK_GT(frames, 0); |
snd_pcm_sframes_t frames_written = |
- wrapper_->PcmWritei(playback_handle_, buffer_pos, frames); |
+ wrapper_->PcmWritei(playback_handle_, buffer_data, frames); |
if (frames_written < 0) { |
// Attempt once to immediately recover from EINTR, |
// EPIPE (overrun/underrun), ESTRPIPE (stream suspended). WritePacket |
@@ -599,7 +590,14 @@ void AlsaPcmOutputStream::WritePacket(Packet* packet) { |
stop_stream_ = true; |
} |
} else { |
- packet->used += frames_written * bytes_per_output_frame_; |
+ if (frames_written > frames) { |
+ LOG(WARNING) |
+ << "snd_pcm_writei() has written more frame that we asked."; |
+ frames_written = frames; |
+ } |
+ |
+ // Seek forward in the buffer after we've written some data to ALSA. |
+ buffer_->Seek(frames_written * bytes_per_output_frame_); |
} |
} |
} |
@@ -611,25 +609,22 @@ void AlsaPcmOutputStream::WriteTask() { |
return; |
} |
- BufferPacket(packet_.get()); |
- WritePacket(packet_.get()); |
+ BufferPacket(); |
+ WritePacket(); |
- ScheduleNextWrite(packet_.get()); |
+ ScheduleNextWrite(); |
} |
-void AlsaPcmOutputStream::ScheduleNextWrite(Packet* current_packet) { |
+void AlsaPcmOutputStream::ScheduleNextWrite() { |
DCHECK_EQ(MessageLoop::current(), message_loop_); |
if (stop_stream_) { |
return; |
} |
- // Calculate when we should have enough buffer for another packet of data. |
- // Make sure to take into consideration down-mixing. |
- uint32 frames_leftover = |
- FramesInPacket(*current_packet, bytes_per_output_frame_); |
- uint32 frames_avail_wanted = |
- (frames_leftover > 0) ? frames_leftover : frames_per_packet_; |
+ // Next write is scheduled for the moment when half of the buffer is |
+ // available. |
+ uint32 frames_avail_wanted = alsa_buffer_frames_ / 2; |
uint32 available_frames = GetAvailableFrames(); |
uint32 next_fill_time_ms = 0; |
@@ -649,13 +644,10 @@ void AlsaPcmOutputStream::ScheduleNextWrite(Packet* current_packet) { |
} |
// Avoid busy looping if the data source is exhausted. |
- if (current_packet->size == 0) { |
+ if (buffer_->forward_bytes() == 0) { |
next_fill_time_ms = std::max(next_fill_time_ms, kNoDataSleepMilliseconds); |
} |
- // Wake up sooner than should be necessary to avoid stutter. |
- next_fill_time_ms /= 2; // TODO(fbarchard): Remove this hack. |
- |
// Only schedule more reads/writes if we are still in the playing state. |
if (shared_data_.state() == kIsPlaying) { |
if (next_fill_time_ms == 0) { |
@@ -673,11 +665,6 @@ void AlsaPcmOutputStream::ScheduleNextWrite(Packet* current_packet) { |
} |
} |
-uint32 AlsaPcmOutputStream::FramesInPacket(const Packet& packet, |
- uint32 bytes_per_frame) { |
- return (packet.size - packet.used) / bytes_per_frame; |
-} |
- |
uint32 AlsaPcmOutputStream::FramesToMicros(uint32 frames, uint32 sample_rate) { |
return frames * base::Time::kMicrosecondsPerSecond / sample_rate; |
} |
@@ -805,6 +792,29 @@ snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() { |
return available_frames; |
} |
+snd_pcm_sframes_t AlsaPcmOutputStream::GetCurrentDelay() { |
+ snd_pcm_sframes_t delay = 0; |
+ |
+ // Don't query ALSA's delay if we have underrun since it'll be jammed at |
+ // some non-zero value and potentially even negative! |
+ if (wrapper_->PcmState(playback_handle_) != SND_PCM_STATE_XRUN) { |
+ int error = wrapper_->PcmDelay(playback_handle_, &delay); |
+ if (error < 0) { |
+ // Assume a delay of zero and attempt to recover the device. |
+ delay = 0; |
+ error = wrapper_->PcmRecover(playback_handle_, |
+ error, |
+ kPcmRecoverIsSilent); |
+ if (error < 0) { |
+ LOG(ERROR) << "Failed querying delay: " << wrapper_->StrError(error); |
+ } |
+ } |
+ if (delay < 0) |
+ delay = 0; |
+ } |
+ return delay; |
+} |
+ |
snd_pcm_t* AlsaPcmOutputStream::AutoSelectDevice(unsigned int latency) { |
// For auto-selection: |
// 1) Attempt to open a device that best matches the number of channels |