summaryrefslogtreecommitdiffstats
path: root/media/audio
diff options
context:
space:
mode:
authorajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-16 18:41:56 +0000
committerajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-16 18:41:56 +0000
commit713b99db0f33455c019968d6ff890e9830ebd4f0 (patch)
treeeefc378f9c6a469a25ad1d1653fac72e906bb6a1 /media/audio
parent4576ba04a833ea439f6dd034eeced33b24c898e9 (diff)
downloadchromium_src-713b99db0f33455c019968d6ff890e9830ebd4f0.zip
chromium_src-713b99db0f33455c019968d6ff890e9830ebd4f0.tar.gz
chromium_src-713b99db0f33455c019968d6ff890e9830ebd4f0.tar.bz2
Move Alsa device opening into the audio thread, and add in support for multi-channel audio.
Moving the device opening into the audio thread will prevent browser hangs for badly behaving alsa implementations (like pulseaudio) that can hang snd_pcm_open if pulseaudiod is wedged, even if SND_PCM_NONBLOCK is requested. For multi-channel audio, device enumeration has been added to try and find a multi-channel device with a stable channel mapping. According to http://0pointer.de/blog/projects/guide-to-sound-apis.html, default should only be used with mono and stereo stream because the channel ordering is not defined by Alsa. To get a well-defined channel ordering, one must use one of the surround40, surround51, etc., device names. However, these device names do not always allow multiple opens, so a fallback scheme is implemented to use default if necessary. BUG=20945,17703 TEST=listened with built-in soundcard and USB soundcard with various other audio programs running. Review URL: http://codereview.chromium.org/275022 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@29299 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-rw-r--r--media/audio/linux/alsa_output.cc388
-rw-r--r--media/audio/linux/alsa_output.h40
-rw-r--r--media/audio/linux/alsa_output_unittest.cc310
-rw-r--r--media/audio/linux/alsa_wrapper.cc12
-rw-r--r--media/audio/linux/alsa_wrapper.h4
-rw-r--r--media/audio/linux/audio_manager_linux.cc18
-rw-r--r--media/audio/mac/audio_output_mac.cc3
7 files changed, 656 insertions, 119 deletions
diff --git a/media/audio/linux/alsa_output.cc b/media/audio/linux/alsa_output.cc
index 9f3498e..53dc14b 100644
--- a/media/audio/linux/alsa_output.cc
+++ b/media/audio/linux/alsa_output.cc
@@ -102,13 +102,20 @@ static const int kPcmRecoverIsSilent = 0;
#endif
const char AlsaPcmOutputStream::kDefaultDevice[] = "default";
+const char AlsaPcmOutputStream::kAutoSelectDevice[] = "";
+const char AlsaPcmOutputStream::kPlugPrefix[] = "plug:";
+
+// Since we expect to only be able to wake up with a resolution of
+// kSleepErrorMilliseconds, double that for our minimum required latency.
+const int AlsaPcmOutputStream::kMinLatencyMicros =
+ kSleepErrorMilliseconds * 2 * 1000;
namespace {
snd_pcm_format_t BitsToFormat(char bits_per_sample) {
switch (bits_per_sample) {
case 8:
- return SND_PCM_FORMAT_S8;
+ return SND_PCM_FORMAT_U8;
case 16:
return SND_PCM_FORMAT_S16;
@@ -124,8 +131,79 @@ snd_pcm_format_t BitsToFormat(char bits_per_sample) {
}
}
+// While the "default" device may support multi-channel audio, in Alsa, only
+// the device names surround40, surround41, surround50, etc, have a defined
+// channel mapping according to Lennart:
+//
+// http://0pointer.de/blog/projects/guide-to-sound-apis.html
+//
+// This function makes a best guess at the specific > 2 channel device name
+// based on the number of channels requested. NULL is returned if no device
+// can be found to match the channel numbers. In this case, using
+// kDefaultDevice is probably the best bet.
+//
+// A five channel source is assumed to be surround50 instead of surround41
+// (which is also 5 channels).
+//
+// TODO(ajwong): The source data should have enough info to tell us if we want
+// surround41 versus surround51, etc., instead of needing us to guess base don
+// channel number. Fix API to pass that data down.
+const char* GuessSpecificDeviceName(int channels) {
+ switch (channels) {
+ case 8:
+ return "surround71";
+
+ case 7:
+ return "surround70";
+
+ case 6:
+ return "surround51";
+
+ case 5:
+ return "surround50";
+
+ case 4:
+ return "surround40";
+
+ default:
+ return NULL;
+ }
+}
+
+// Reorder PCM from AAC layout to Alsa layout.
+// TODO(fbarchard): Switch layout when ffmpeg is updated.
+template<class Format>
+static void Swizzle50Layout(Format* b, size_t filled) {
+ static const int kNumSurroundChannels = 5;
+ Format aac[kNumSurroundChannels];
+ for (size_t i = 0; i < filled; i += sizeof(aac), b += kNumSurroundChannels) {
+ memcpy(aac, b, sizeof(aac));
+ b[0] = aac[1]; // L
+ b[1] = aac[2]; // R
+ b[2] = aac[3]; // Ls
+ b[3] = aac[4]; // Rs
+ b[4] = aac[0]; // C
+ }
+}
+
+template<class Format>
+static void Swizzle51Layout(Format* b, size_t filled) {
+ static const int kNumSurroundChannels = 6;
+ Format aac[kNumSurroundChannels];
+ for (size_t i = 0; i < filled; i += sizeof(aac), b += kNumSurroundChannels) {
+ memcpy(aac, b, sizeof(aac));
+ b[0] = aac[1]; // L
+ b[1] = aac[2]; // R
+ b[2] = aac[3]; // Ls
+ b[3] = aac[4]; // Rs
+ b[4] = aac[0]; // C
+ b[5] = aac[5]; // LFE
+ }
+}
+
} // namespace
+// Not in an anonymous so that it can be a friend to AlsaPcmOutputStream.
std::ostream& operator<<(std::ostream& os,
AlsaPcmOutputStream::InternalState state) {
switch (state) {
@@ -160,12 +238,16 @@ AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name,
AudioManagerLinux* manager,
MessageLoop* message_loop)
: shared_data_(MessageLoop::current()),
- device_name_(device_name),
+ requested_device_name_(device_name),
pcm_format_(BitsToFormat(bits_per_sample)),
channels_(channels),
sample_rate_(sample_rate),
bytes_per_sample_(bits_per_sample / 8),
bytes_per_frame_(channels_ * bits_per_sample / 8),
+ should_downmix_(false),
+ latency_micros_(0),
+ micros_per_packet_(0),
+ bytes_per_output_frame_(bytes_per_frame_),
stop_stream_(false),
wrapper_(wrapper),
manager_(manager),
@@ -175,16 +257,6 @@ AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name,
message_loop_(message_loop) {
// Sanity check input values.
- //
- // TODO(scherkus): ALSA works fine if you pass in multichannel audio, however
- // it seems to be mapped to the wrong channels. We may have to do some
- // channel swizzling from decoder output to ALSA's preferred multichannel
- // format.
- if (channels_ != 1 && channels_ != 2) {
- LOG(WARNING) << "Only 1 and 2 channel audio is supported right now.";
- shared_data_.TransitionTo(kInError);
- }
-
if (AudioManager::AUDIO_PCM_LINEAR != format) {
LOG(WARNING) << "Only linear PCM supported.";
shared_data_.TransitionTo(kInError);
@@ -222,36 +294,6 @@ bool AlsaPcmOutputStream::Open(size_t packet_size) {
return false;
}
- // Try to open the device.
- snd_pcm_t* handle = NULL;
- int error = wrapper_->PcmOpen(&handle, device_name_.c_str(),
- SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
- if (error < 0) {
- LOG(ERROR) << "Cannot open audio device (" << device_name_ << "): "
- << wrapper_->StrError(error);
- return false;
- }
-
- // Configure the device for software resampling, and add enough buffer for
- // two audio packets.
- int micros_per_packet =
- FramesToMicros(packet_size / bytes_per_frame_, sample_rate_);
- if ((error = wrapper_->PcmSetParams(handle,
- pcm_format_,
- SND_PCM_ACCESS_RW_INTERLEAVED,
- channels_,
- sample_rate_,
- 1, // soft_resample -- let ALSA resample
- micros_per_packet * 2)) < 0) {
- LOG(ERROR) << "Unable to set PCM parameters for (" << device_name_
- << "): " << wrapper_->StrError(error);
- if (!CloseDevice(handle)) {
- // TODO(ajwong): Retry on certain errors?
- LOG(WARNING) << "Unable to close audio device. Leaking handle.";
- }
- return false;
- }
-
// We do not need to check if the transition was successful because
// CanTransitionTo() was checked above, and it is assumed that this
// object's public API is only called on one thread so the state cannot
@@ -259,8 +301,7 @@ bool AlsaPcmOutputStream::Open(size_t packet_size) {
shared_data_.TransitionTo(kIsOpened);
message_loop_->PostTask(
FROM_HERE,
- NewRunnableMethod(this, &AlsaPcmOutputStream::FinishOpen,
- handle, packet_size));
+ NewRunnableMethod(this, &AlsaPcmOutputStream::OpenTask, packet_size));
return true;
}
@@ -321,18 +362,45 @@ void AlsaPcmOutputStream::GetVolume(double* left_level, double* right_level) {
*left_level = *right_level = shared_data_.volume();
}
-void AlsaPcmOutputStream::FinishOpen(snd_pcm_t* playback_handle,
- size_t packet_size) {
+void AlsaPcmOutputStream::OpenTask(size_t packet_size) {
DCHECK_EQ(MessageLoop::current(), message_loop_);
- playback_handle_ = playback_handle;
- packet_.reset(new Packet(packet_size));
+ // Initialize the configuration variables.
frames_per_packet_ = packet_size / bytes_per_frame_;
+
+ // Try to open the device.
+ micros_per_packet_ =
+ FramesToMicros(packet_size / bytes_per_frame_, sample_rate_);
+ latency_micros_ = std::max(AlsaPcmOutputStream::kMinLatencyMicros,
+ micros_per_packet_ * 2);
+ if (requested_device_name_ == kAutoSelectDevice) {
+ playback_handle_ = AutoSelectDevice(latency_micros_);
+ if (playback_handle_) {
+ LOG(INFO) << "Auto-selected device: " << device_name_;
+ }
+ } else {
+ device_name_ = requested_device_name_;
+ playback_handle_ = OpenDevice(device_name_, channels_, latency_micros_);
+ }
+
+ // Finish initializing the stream if the device was opened successfully.
+ 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_;
+ }
+ }
}
void AlsaPcmOutputStream::StartTask() {
DCHECK_EQ(MessageLoop::current(), message_loop_);
+ if (stop_stream_) {
+ return;
+ }
+
// When starting again, drop all packets in the device and prepare it again
// incase we are restarting from a pause state and need to flush old data.
int error = wrapper_->PcmDrop(playback_handle_);
@@ -353,14 +421,15 @@ void AlsaPcmOutputStream::StartTask() {
return;
}
- // Do a best-effort write of 2 packets to pre-roll.
+ // 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): Make this track with the us_latency set in Open().
- // Also handle EAGAIN.
- BufferPacket(packet_.get());
- WritePacket(packet_.get());
- BufferPacket(packet_.get());
- WritePacket(packet_.get());
+ // TODO(ajwong): Handle EAGAIN.
+ const int num_preroll = latency_micros_ / micros_per_packet_;
+ for (int i = 0; i < num_preroll; ++i) {
+ BufferPacket(packet_.get());
+ WritePacket(packet_.get());
+ }
ScheduleNextWrite(packet_.get());
}
@@ -410,7 +479,7 @@ void AlsaPcmOutputStream::BufferPacket(Packet* packet) {
// the playback and report an error.
delay = 0;
} else {
- delay *= bytes_per_frame_;
+ delay *= bytes_per_output_frame_;
}
packet->used = 0;
@@ -425,18 +494,62 @@ void AlsaPcmOutputStream::BufferPacket(Packet* packet) {
DCHECK(packet->size % bytes_per_frame_ == 0);
packet->size = (packet->size / bytes_per_frame_) * bytes_per_frame_;
- media::AdjustVolume(packet->buffer.get(),
- packet->size,
- channels_,
- bytes_per_sample_,
- shared_data_.volume());
+ if (should_downmix_) {
+ if (media::FoldChannels(packet->buffer.get(),
+ 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_;
+ } else {
+ LOG(ERROR) << "Folding failed";
+ }
+ } else {
+ // TODO(ajwong): Handle other channel orderings.
+
+ // Handle channel order for 5.0 audio.
+ if (channels_ == 5) {
+ if (bytes_per_sample_ == 1) {
+ Swizzle50Layout(reinterpret_cast<uint8*>(packet->buffer.get()),
+ packet->size);
+ } else if (bytes_per_sample_ == 2) {
+ Swizzle50Layout(reinterpret_cast<int16*>(packet->buffer.get()),
+ packet->size);
+ } else if (bytes_per_sample_ == 4) {
+ Swizzle50Layout(reinterpret_cast<int32*>(packet->buffer.get()),
+ 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);
+ } else if (bytes_per_sample_ == 2) {
+ Swizzle51Layout(reinterpret_cast<int16*>(packet->buffer.get()),
+ packet->size);
+ } else if (bytes_per_sample_ == 4) {
+ Swizzle51Layout(reinterpret_cast<int32*>(packet->buffer.get()),
+ packet->size);
+ }
+ }
+
+ media::AdjustVolume(packet->buffer.get(),
+ packet->size,
+ channels_,
+ bytes_per_sample_,
+ shared_data_.volume());
+ }
}
}
void AlsaPcmOutputStream::WritePacket(Packet* packet) {
DCHECK_EQ(MessageLoop::current(), message_loop_);
- CHECK(packet->size % bytes_per_frame_ == 0);
+ CHECK(packet->size % bytes_per_output_frame_ == 0);
// If the device is in error, just eat the bytes.
if (stop_stream_) {
@@ -446,7 +559,7 @@ void AlsaPcmOutputStream::WritePacket(Packet* packet) {
if (packet->used < packet->size) {
char* buffer_pos = packet->buffer.get() + packet->used;
- snd_pcm_sframes_t frames = FramesInPacket(*packet, bytes_per_frame_);
+ snd_pcm_sframes_t frames = FramesInPacket(*packet, bytes_per_output_frame_);
DCHECK_GT(frames, 0);
@@ -472,7 +585,7 @@ void AlsaPcmOutputStream::WritePacket(Packet* packet) {
stop_stream_ = true;
}
} else {
- packet->used += frames_written * bytes_per_frame_;
+ packet->used += frames_written * bytes_per_output_frame_;
}
}
}
@@ -498,10 +611,12 @@ void AlsaPcmOutputStream::ScheduleNextWrite(Packet* current_packet) {
}
// Calculate when we should have enough buffer for another packet of data.
- int frames_leftover = FramesInPacket(*current_packet, bytes_per_frame_);
- int frames_needed =
- frames_leftover > 0 ? frames_leftover : frames_per_packet_;
- int frames_until_empty_enough = frames_needed - GetAvailableFrames();
+ // Make sure to take into consideration down-mixing.
+ int frames_leftover =
+ FramesInPacket(*current_packet, bytes_per_output_frame_);
+ int frames_avail_wanted =
+ (frames_leftover > 0) ? frames_leftover : frames_per_packet_;
+ int frames_until_empty_enough = frames_avail_wanted - GetAvailableFrames();
int next_fill_time_ms =
FramesToMillis(frames_until_empty_enough, sample_rate_);
@@ -545,6 +660,89 @@ int64 AlsaPcmOutputStream::FramesToMillis(int frames, int sample_rate) {
return frames * base::Time::kMillisecondsPerSecond / sample_rate;
}
+std::string AlsaPcmOutputStream::FindDeviceForChannels(int channels) {
+ // Constants specified by the ALSA API for device hints.
+ static const int kGetAllDevices = -1;
+ static const char kPcmInterfaceName[] = "pcm";
+ static const char kIoHintName[] = "IOID";
+ static const char kNameHintName[] = "NAME";
+
+ const char* wanted_device = GuessSpecificDeviceName(channels);
+ if (!wanted_device) {
+ return "";
+ }
+
+ std::string guessed_device;
+ void** hints = NULL;
+ int error = wrapper_->DeviceNameHint(kGetAllDevices,
+ kPcmInterfaceName,
+ &hints);
+ if (error == 0) {
+ // NOTE: Do not early return from inside this if statement. The
+ // hints above need to be freed.
+ for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
+ // Only examine devices that are output capable.. Valid values are
+ // "Input", "Output", and NULL which means both input and output.
+ scoped_ptr_malloc<char> io(
+ wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName));
+ if (io != NULL && strcmp(io.get(), "Input") == 0)
+ continue;
+
+ // Attempt to select the closest device for number of channels.
+ scoped_ptr_malloc<char> name(
+ wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName));
+ if (strncmp(wanted_device, name.get(), strlen(wanted_device)) == 0) {
+ guessed_device = name.get();
+ break;
+ }
+ }
+
+ // Destory the hint now that we're done with it.
+ wrapper_->DeviceNameFreeHint(hints);
+ hints = NULL;
+ } else {
+ LOG(ERROR) << "Unable to get hints for devices: "
+ << wrapper_->StrError(error);
+ }
+
+ return guessed_device;
+}
+
+snd_pcm_t* AlsaPcmOutputStream::OpenDevice(const std::string& device_name,
+ int channels,
+ unsigned int latency) {
+ snd_pcm_t* handle = NULL;
+ int error = wrapper_->PcmOpen(&handle, device_name.c_str(),
+ SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (error < 0) {
+ LOG(ERROR) << "Cannot open audio device (" << device_name << "): "
+ << wrapper_->StrError(error);
+ return NULL;
+ }
+
+ // Configure the device for software resampling.
+ if ((error = wrapper_->PcmSetParams(handle,
+ pcm_format_,
+ SND_PCM_ACCESS_RW_INTERLEAVED,
+ channels,
+ sample_rate_,
+ 1, // soft_resample -- let ALSA resample
+ latency)) < 0) {
+ LOG(ERROR) << "Unable to set PCM parameters for (" << device_name
+ << "): " << wrapper_->StrError(error)
+ << " -- Format: " << pcm_format_
+ << " Channels: " << channels
+ << " Latency (us): " << latency;
+ if (!CloseDevice(handle)) {
+ // TODO(ajwong): Retry on certain errors?
+ LOG(WARNING) << "Unable to close audio device. Leaking handle.";
+ }
+ return NULL;
+ }
+
+ return handle;
+}
+
bool AlsaPcmOutputStream::CloseDevice(snd_pcm_t* handle) {
int error = wrapper_->PcmClose(handle);
if (error < 0) {
@@ -580,6 +778,60 @@ snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() {
return available_frames;
}
+snd_pcm_t* AlsaPcmOutputStream::AutoSelectDevice(unsigned int latency) {
+ // For auto-selection:
+ // 1) Attempt to open a device that best matches the number of channels
+ // requested.
+ // 2) If that fails, attempt the "plug:" version of it incase ALSA can
+ // remap do some software conversion to make it work.
+ // 3) Fallback to kDefaultDevice.
+ // 4) If that fails too, try the "plug:" version of kDefaultDevice.
+ // 5) Give up.
+ snd_pcm_t* handle = NULL;
+ device_name_ = FindDeviceForChannels(channels_);
+
+ // Step 1.
+ if (!device_name_.empty()) {
+ if ((handle = OpenDevice(device_name_, channels_, latency)) != NULL) {
+ return handle;
+ }
+
+ // Step 2.
+ device_name_ = kPlugPrefix + device_name_;
+ if ((handle = OpenDevice(device_name_, channels_, latency)) != NULL) {
+ return handle;
+ }
+ }
+
+ // For the kDefaultDevice device, we can only reliably depend on 2-channel
+ // output to have the correct ordering according to Lennart. For the channel
+ // formats that we know how to downmix from (5 channel to 6 channel), setup
+ // downmixing.
+ //
+ // TODO(ajwong): We need a SupportsFolding() function.
+ int default_channels = channels_;
+ if (default_channels >= 5 && default_channels <= 6) {
+ should_downmix_ = true;
+ default_channels = 2;
+ }
+
+ // Step 3.
+ device_name_ = kDefaultDevice;
+ if ((handle = OpenDevice(device_name_, default_channels, latency)) != NULL) {
+ return handle;
+ }
+
+ // Step 4.
+ device_name_ = kPlugPrefix + device_name_;
+ if ((handle = OpenDevice(device_name_, default_channels, latency)) != NULL) {
+ return handle;
+ }
+
+ // Unable to open any device.
+ device_name_.clear();
+ return NULL;
+}
+
AudioManagerLinux* AlsaPcmOutputStream::manager() {
DCHECK_EQ(MessageLoop::current(), client_thread_loop_);
return manager_;
diff --git a/media/audio/linux/alsa_output.h b/media/audio/linux/alsa_output.h
index 409b158..5bdaa1b 100644
--- a/media/audio/linux/alsa_output.h
+++ b/media/audio/linux/alsa_output.h
@@ -46,18 +46,27 @@ class AlsaPcmOutputStream :
public AudioOutputStream,
public base::RefCountedThreadSafe<AlsaPcmOutputStream> {
public:
- // Set to "default" which should avoid locking the sound device and allow
- // ALSA to multiplex sound from different processes that want to write PCM
- // data.
+ // String for the generic "default" ALSA device that has the highest
+ // compatibility and chance of working.
static const char kDefaultDevice[];
+ // Pass this to the AlsaPcmOutputStream if you want to attempt auto-selection
+ // of the audio device.
+ static const char kAutoSelectDevice[];
+
+ // Prefix for device names to enable ALSA library resampling.
+ static const char kPlugPrefix[];
+
+ // The minimum latency that is accepted by the device.
+ static const int kMinLatencyMicros;
+
// Create a PCM Output stream for the ALSA device identified by
// |device_name|. The AlsaPcmOutputStream uses |wrapper| to communicate with
// the alsa libraries, allowing for dependency injection during testing. All
// requesting of data, and writing to the alsa device will be done on
// |message_loop|.
//
- // If unsure of what to use for |device_name|, use |kDefaultDevice|.
+ // If unsure of what to use for |device_name|, use |kAutoSelectDevice|.
AlsaPcmOutputStream(const std::string& device_name,
AudioManager::Format format,
int channels,
@@ -78,10 +87,14 @@ class AlsaPcmOutputStream :
private:
friend class AlsaPcmOutputStreamTest;
+ FRIEND_TEST(AlsaPcmOutputStreamTest, AutoSelectDevice_DeviceSelect);
+ FRIEND_TEST(AlsaPcmOutputStreamTest, AutoSelectDevice_FallbackDevices);
+ FRIEND_TEST(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail);
FRIEND_TEST(AlsaPcmOutputStreamTest, BufferPacket);
FRIEND_TEST(AlsaPcmOutputStreamTest, BufferPacket_StopStream);
FRIEND_TEST(AlsaPcmOutputStreamTest, BufferPacket_UnfinishedPacket);
FRIEND_TEST(AlsaPcmOutputStreamTest, ConstructedState);
+ FRIEND_TEST(AlsaPcmOutputStreamTest, LatencyFloor);
FRIEND_TEST(AlsaPcmOutputStreamTest, OpenClose);
FRIEND_TEST(AlsaPcmOutputStreamTest, PcmOpenFailed);
FRIEND_TEST(AlsaPcmOutputStreamTest, PcmSetParamsFailed);
@@ -121,7 +134,7 @@ class AlsaPcmOutputStream :
friend std::ostream& ::operator<<(std::ostream& os, InternalState);
// Various tasks that complete actions started in the public API.
- void FinishOpen(snd_pcm_t* playback_handle, size_t packet_size);
+ void OpenTask(size_t packet_size);
void StartTask();
void CloseTask();
@@ -137,9 +150,17 @@ class AlsaPcmOutputStream :
int bytes_per_frame);
static int64 FramesToMicros(int frames, int sample_rate);
static int64 FramesToMillis(int frames, int sample_rate);
+ std::string FindDeviceForChannels(int channels);
+ snd_pcm_t* OpenDevice(const std::string& device_name,
+ int channels,
+ unsigned int latency);
bool CloseDevice(snd_pcm_t* handle);
snd_pcm_sframes_t GetAvailableFrames();
+ // Attempts to find the best matching linux audio device for the given number
+ // of channels. This function will set |device_name_| and |should_downmix_|.
+ snd_pcm_t* AutoSelectDevice(unsigned int latency);
+
// Thread-asserting accessors for member variables.
AudioManagerLinux* manager();
@@ -192,13 +213,20 @@ class AlsaPcmOutputStream :
// Configuration constants from the constructor. Referenceable by all threads
// since they are constants.
- const std::string device_name_;
+ const std::string requested_device_name_;
const snd_pcm_format_t pcm_format_;
const int channels_;
const int sample_rate_;
const int bytes_per_sample_;
const int bytes_per_frame_;
+ // Device configuration data. Populated after OpenTask() completes.
+ std::string device_name_;
+ bool should_downmix_;
+ int latency_micros_;
+ int micros_per_packet_;
+ int bytes_per_output_frame_;
+
// Flag indicating the code should stop reading from the data source or
// writing to the ALSA device. This is set because the device has entered
// an unrecoverable error state, or the ClosedTask() has executed.
diff --git a/media/audio/linux/alsa_output_unittest.cc b/media/audio/linux/alsa_output_unittest.cc
index e20526e..72c4c90 100644
--- a/media/audio/linux/alsa_output_unittest.cc
+++ b/media/audio/linux/alsa_output_unittest.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "base/logging.h"
+#include "base/string_util.h"
#include "media/audio/linux/alsa_output.h"
#include "media/audio/linux/alsa_wrapper.h"
#include "media/audio/linux/audio_manager_linux.h"
@@ -12,13 +13,24 @@
using testing::_;
using testing::DoAll;
using testing::Eq;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Mock;
+using testing::MockFunction;
using testing::Return;
using testing::SetArgumentPointee;
using testing::StrictMock;
using testing::StrEq;
+using testing::Unused;
class MockAlsaWrapper : public AlsaWrapper {
public:
+ MOCK_METHOD3(DeviceNameHint, int(int card,
+ const char* iface,
+ void*** hints));
+ MOCK_METHOD2(DeviceNameGetHint, char*(const void* hint, const char* id));
+ MOCK_METHOD1(DeviceNameFreeHint, int(void** hints));
+
MOCK_METHOD4(PcmOpen, int(snd_pcm_t** handle, const char* name,
snd_pcm_stream_t stream, int mode));
MOCK_METHOD1(PcmClose, int(snd_pcm_t* handle));
@@ -64,15 +76,7 @@ class AlsaPcmOutputStreamTest : public testing::Test {
protected:
AlsaPcmOutputStreamTest()
: packet_(kTestPacketSize + 1) {
- test_stream_ = new AlsaPcmOutputStream(kTestDeviceName,
- kTestFormat,
- kTestChannels,
- kTestSampleRate,
- kTestBitsPerSample,
- &mock_alsa_wrapper_,
- &mock_manager_,
- &message_loop_);
-
+ test_stream_ = CreateStreamWithChannels(kTestChannels);
packet_.size = kTestPacketSize;
}
@@ -80,6 +84,27 @@ class AlsaPcmOutputStreamTest : public testing::Test {
test_stream_ = NULL;
}
+ AlsaPcmOutputStream* CreateStreamWithChannels(int channels) {
+ return new AlsaPcmOutputStream(kTestDeviceName,
+ kTestFormat,
+ channels,
+ kTestSampleRate,
+ kTestBitsPerSample,
+ &mock_alsa_wrapper_,
+ &mock_manager_,
+ &message_loop_);
+ }
+
+ // Helper function to malloc the string returned by DeviceNameHint for NAME.
+ static char* EchoHint(const void* name, Unused) {
+ return strdup(static_cast<const char*>(name));
+ }
+
+ // Helper function to malloc the string returned by DeviceNameHint for IOID.
+ static char* OutputHint(Unused, Unused) {
+ return strdup("Output");
+ }
+
static const int kTestChannels;
static const int kTestSampleRate;
static const int kTestBitsPerSample;
@@ -92,6 +117,15 @@ class AlsaPcmOutputStreamTest : public testing::Test {
static const int kTestFailedErrno;
static snd_pcm_t* const kFakeHandle;
+ // Used to simulate DeviceNameHint.
+ static char kSurround40[];
+ static char kSurround41[];
+ static char kSurround50[];
+ static char kSurround51[];
+ static char kSurround70[];
+ static char kSurround71[];
+ static void* kFakeHints[];
+
StrictMock<MockAlsaWrapper> mock_alsa_wrapper_;
StrictMock<MockAudioManagerLinux> mock_manager_;
MessageLoop message_loop_;
@@ -113,7 +147,7 @@ const AudioManager::Format AlsaPcmOutputStreamTest::kTestFormat =
AudioManager::AUDIO_PCM_LINEAR;
const char AlsaPcmOutputStreamTest::kTestDeviceName[] = "TestDevice";
const char AlsaPcmOutputStreamTest::kDummyMessage[] = "dummy";
-const int AlsaPcmOutputStreamTest::kTestFramesPerPacket = 100;
+const int AlsaPcmOutputStreamTest::kTestFramesPerPacket = 1000;
const size_t AlsaPcmOutputStreamTest::kTestPacketSize =
AlsaPcmOutputStreamTest::kTestFramesPerPacket *
AlsaPcmOutputStreamTest::kTestBytesPerFrame;
@@ -121,32 +155,28 @@ const int AlsaPcmOutputStreamTest::kTestFailedErrno = -EACCES;
snd_pcm_t* const AlsaPcmOutputStreamTest::kFakeHandle =
reinterpret_cast<snd_pcm_t*>(1);
+char AlsaPcmOutputStreamTest::kSurround40[] = "surround40:CARD=foo,DEV=0";
+char AlsaPcmOutputStreamTest::kSurround41[] = "surround41:CARD=foo,DEV=0";
+char AlsaPcmOutputStreamTest::kSurround50[] = "surround50:CARD=foo,DEV=0";
+char AlsaPcmOutputStreamTest::kSurround51[] = "surround51:CARD=foo,DEV=0";
+char AlsaPcmOutputStreamTest::kSurround70[] = "surround70:CARD=foo,DEV=0";
+char AlsaPcmOutputStreamTest::kSurround71[] = "surround71:CARD=foo,DEV=0";
+void* AlsaPcmOutputStreamTest::kFakeHints[] = {
+ kSurround40, kSurround41, kSurround50, kSurround51,
+ kSurround70, kSurround71, NULL };
+
TEST_F(AlsaPcmOutputStreamTest, ConstructedState) {
EXPECT_EQ(AlsaPcmOutputStream::kCreated,
test_stream_->shared_data_.state());
// Should support mono.
- test_stream_ = new AlsaPcmOutputStream(kTestDeviceName,
- kTestFormat,
- 1, // Channels.
- kTestSampleRate,
- kTestBitsPerSample,
- &mock_alsa_wrapper_,
- &mock_manager_,
- &message_loop_);
+ test_stream_ = CreateStreamWithChannels(1);
EXPECT_EQ(AlsaPcmOutputStream::kCreated,
test_stream_->shared_data_.state());
- // Should not support multi-channel.
- test_stream_ = new AlsaPcmOutputStream(kTestDeviceName,
- kTestFormat,
- 3, // Channels.
- kTestSampleRate,
- kTestBitsPerSample,
- &mock_alsa_wrapper_,
- &mock_manager_,
- &message_loop_);
- EXPECT_EQ(AlsaPcmOutputStream::kInError,
+ // Should support multi-channel.
+ test_stream_ = CreateStreamWithChannels(3);
+ EXPECT_EQ(AlsaPcmOutputStream::kCreated,
test_stream_->shared_data_.state());
// Bad bits per sample.
@@ -174,6 +204,68 @@ TEST_F(AlsaPcmOutputStreamTest, ConstructedState) {
test_stream_->shared_data_.state());
}
+TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
+ const double kMicrosPerFrame =
+ static_cast<double>(1000000) / kTestSampleRate;
+ const double kPacketFramesInMinLatency =
+ AlsaPcmOutputStream::kMinLatencyMicros / kMicrosPerFrame / 2.0;
+ const int kMinLatencyPacketSize =
+ static_cast<int>(kPacketFramesInMinLatency * kTestBytesPerFrame);
+
+ // Test that packets which would cause a latency under less than
+ // AlsaPcmOutputStream::kMinLatencyMicros will get clipped to
+ // AlsaPcmOutputStream::kMinLatencyMicros,
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
+ .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
+ Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmSetParams(_, _, _, _, _, _,
+ AlsaPcmOutputStream::kMinLatencyMicros))
+ .WillOnce(Return(0));
+
+ ASSERT_TRUE(test_stream_->Open(kMinLatencyPacketSize));
+ message_loop_.RunAllPending();
+
+ // Now close it and test that everything was released.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) .WillOnce(Return(0));
+ EXPECT_CALL(mock_manager_, ReleaseStream(test_stream_.get()));
+ test_stream_->Close();
+ message_loop_.RunAllPending();
+
+ Mock::VerifyAndClear(&mock_alsa_wrapper_);
+ Mock::VerifyAndClear(&mock_manager_);
+
+ // Test that having more packets ends up with a latency based on packet size.
+ const int kOverMinLatencyPacketSize =
+ (kPacketFramesInMinLatency + 1) * kTestBytesPerFrame;
+ int64 expected_micros = 2 *
+ AlsaPcmOutputStream::FramesToMicros(
+ kOverMinLatencyPacketSize / kTestBytesPerFrame,
+ kTestSampleRate);
+
+ // Recreate the stream to reset the state.
+ test_stream_ = CreateStreamWithChannels(kTestChannels);
+
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
+ .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmSetParams(_, _, _, _, _, _, expected_micros))
+ .WillOnce(Return(0));
+
+ ASSERT_TRUE(test_stream_->Open(kOverMinLatencyPacketSize));
+ message_loop_.RunAllPending();
+
+ // Now close it and test that everything was released.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_manager_, ReleaseStream(test_stream_.get()));
+ test_stream_->Close();
+ message_loop_.RunAllPending();
+
+ Mock::VerifyAndClear(&mock_alsa_wrapper_);
+ Mock::VerifyAndClear(&mock_manager_);
+}
+
TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
int64 expected_micros = 2 *
AlsaPcmOutputStream::FramesToMicros(kTestPacketSize / kTestBytesPerFrame,
@@ -189,7 +281,7 @@ TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
Return(0)));
EXPECT_CALL(mock_alsa_wrapper_,
PcmSetParams(kFakeHandle,
- SND_PCM_FORMAT_S8,
+ SND_PCM_FORMAT_U8,
SND_PCM_ACCESS_RW_INTERLEAVED,
kTestChannels,
kTestSampleRate,
@@ -226,11 +318,24 @@ TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) {
EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
.WillOnce(Return(kDummyMessage));
- // If open fails, the stream stays in kCreated because it has effectively had
- // no changes.
- ASSERT_FALSE(test_stream_->Open(kTestPacketSize));
- EXPECT_EQ(AlsaPcmOutputStream::kCreated,
+ // Open still succeeds since PcmOpen is delegated to another thread.
+ ASSERT_TRUE(test_stream_->Open(kTestPacketSize));
+ ASSERT_EQ(AlsaPcmOutputStream::kIsOpened,
test_stream_->shared_data_.state());
+ ASSERT_FALSE(test_stream_->stop_stream_);
+ message_loop_.RunAllPending();
+
+ // Ensure internal state is set for a no-op stream if PcmOpen() failes.
+ EXPECT_EQ(AlsaPcmOutputStream::kIsOpened,
+ test_stream_->shared_data_.state());
+ EXPECT_TRUE(test_stream_->stop_stream_);
+ EXPECT_TRUE(test_stream_->playback_handle_ == NULL);
+ EXPECT_FALSE(test_stream_->packet_.get());
+
+ // Close the stream since we opened it to make destruction happy.
+ EXPECT_CALL(mock_manager_, ReleaseStream(test_stream_.get()));
+ test_stream_->Close();
+ message_loop_.RunAllPending();
}
TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) {
@@ -246,9 +351,23 @@ TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) {
// If open fails, the stream stays in kCreated because it has effectively had
// no changes.
- ASSERT_FALSE(test_stream_->Open(kTestPacketSize));
- EXPECT_EQ(AlsaPcmOutputStream::kCreated,
+ ASSERT_TRUE(test_stream_->Open(kTestPacketSize));
+ EXPECT_EQ(AlsaPcmOutputStream::kIsOpened,
+ test_stream_->shared_data_.state());
+ ASSERT_FALSE(test_stream_->stop_stream_);
+ message_loop_.RunAllPending();
+
+ // Ensure internal state is set for a no-op stream if PcmSetParams() failes.
+ EXPECT_EQ(AlsaPcmOutputStream::kIsOpened,
test_stream_->shared_data_.state());
+ EXPECT_TRUE(test_stream_->stop_stream_);
+ EXPECT_TRUE(test_stream_->playback_handle_ == NULL);
+ EXPECT_FALSE(test_stream_->packet_.get());
+
+ // Close the stream since we opened it to make destruction happy.
+ EXPECT_CALL(mock_manager_, ReleaseStream(test_stream_.get()));
+ test_stream_->Close();
+ message_loop_.RunAllPending();
}
TEST_F(AlsaPcmOutputStreamTest, StartStop) {
@@ -386,6 +505,127 @@ TEST_F(AlsaPcmOutputStreamTest, BufferPacket_UnfinishedPacket) {
EXPECT_EQ(kTestPacketSize, packet_.size);
}
+TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_DeviceSelect) {
+ // Try channels from 1 -> 9. and see that we get the more specific surroundXX
+ // device opened for channels 4-8. For all other channels, the device should
+ // default to |AlsaPcmOutputStream::kDefaultDevice|. We should also not
+ // downmix any channel in this case because downmixing is only defined for
+ // channels 4-8, which we are guaranteeing to work.
+ //
+ // Note that the loop starts at "1", so the first parameter is ignored in
+ // these arrays.
+ const char* kExpectedDeviceName[] = { NULL,
+ AlsaPcmOutputStream::kDefaultDevice,
+ AlsaPcmOutputStream::kDefaultDevice,
+ AlsaPcmOutputStream::kDefaultDevice,
+ kSurround40, kSurround50, kSurround51,
+ kSurround70, kSurround71,
+ AlsaPcmOutputStream::kDefaultDevice };
+ bool kExpectedDownmix[] = { false, false, false, false, false, false,
+ false, false, false, false };
+
+ for (int i = 1; i <= 9; ++i) {
+ SCOPED_TRACE(StringPrintf("Attempting %d Channel", i));
+
+ // Hints will only be grabbed for channel numbers that have non-default
+ // devices associated with them.
+ if (kExpectedDeviceName[i] != AlsaPcmOutputStream::kDefaultDevice) {
+ // The DeviceNameHint and DeviceNameFreeHint need to be paired to avoid a
+ // memory leak.
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
+ .WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0]))
+ .Times(1);
+ }
+
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmOpen(_, StrEq(kExpectedDeviceName[i]), _, _))
+ .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmSetParams(kFakeHandle, _, _, i, _, _, _))
+ .WillOnce(Return(0));
+
+ // The parameters are specified by ALSA documentation, and are in constants
+ // in the implementation files.
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID")))
+ .WillRepeatedly(Invoke(OutputHint));
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
+ .WillRepeatedly(Invoke(EchoHint));
+
+
+ test_stream_ = CreateStreamWithChannels(i);
+ EXPECT_TRUE(test_stream_->AutoSelectDevice(i));
+ EXPECT_EQ(kExpectedDownmix[i], test_stream_->should_downmix_);
+
+ Mock::VerifyAndClearExpectations(&mock_alsa_wrapper_);
+ Mock::VerifyAndClearExpectations(&mock_manager_);
+ }
+}
+
+TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_FallbackDevices) {
+ using std::string;
+
+ // If there are problems opening a multi-channel device, it the fallbacks
+ // operations should be as follows. Assume the multi-channel device name is
+ // surround50:
+ //
+ // 1) Try open "surround50"
+ // 2) Try open "plug:surround50".
+ // 3) Try open "default".
+ // 4) Try open "plug:default".
+ // 5) Give up trying to open.
+ //
+ const string first_try = kSurround50;
+ const string second_try = string(AlsaPcmOutputStream::kPlugPrefix) +
+ kSurround50;
+ const string third_try = AlsaPcmOutputStream::kDefaultDevice;
+ const string fourth_try = string(AlsaPcmOutputStream::kPlugPrefix) +
+ AlsaPcmOutputStream::kDefaultDevice;
+
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
+ .WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0]))
+ .Times(1);
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID")))
+ .WillRepeatedly(Invoke(OutputHint));
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
+ .WillRepeatedly(Invoke(EchoHint));
+ EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
+ .WillRepeatedly(Return(kDummyMessage));
+
+ InSequence s;
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(first_try.c_str()), _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(second_try.c_str()), _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(third_try.c_str()), _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(fourth_try.c_str()), _, _))
+ .WillOnce(Return(kTestFailedErrno));
+
+ test_stream_ = CreateStreamWithChannels(5);
+ EXPECT_FALSE(test_stream_->AutoSelectDevice(5));
+}
+
+TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail) {
+ // Should get |kDefaultDevice|, and force a 2-channel downmix on a failure to
+ // enumerate devices.
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
+ .WillRepeatedly(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmOpen(_, StrEq(AlsaPcmOutputStream::kDefaultDevice), _, _))
+ .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmSetParams(kFakeHandle, _, _, 2, _, _, _))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
+ .WillOnce(Return(kDummyMessage));
+
+ test_stream_ = CreateStreamWithChannels(5);
+ EXPECT_TRUE(test_stream_->AutoSelectDevice(5));
+ EXPECT_TRUE(test_stream_->should_downmix_);
+}
+
TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) {
test_stream_->stop_stream_ = true;
test_stream_->BufferPacket(&packet_);
diff --git a/media/audio/linux/alsa_wrapper.cc b/media/audio/linux/alsa_wrapper.cc
index 8463686..303a1f1 100644
--- a/media/audio/linux/alsa_wrapper.cc
+++ b/media/audio/linux/alsa_wrapper.cc
@@ -17,6 +17,18 @@ int AlsaWrapper::PcmOpen(snd_pcm_t** handle, const char* name,
return snd_pcm_open(handle, name, stream, mode);
}
+int AlsaWrapper::DeviceNameHint(int card, const char* iface, void*** hints) {
+ return snd_device_name_hint(card, iface, hints);
+}
+
+char* AlsaWrapper::DeviceNameGetHint(const void* hint, const char* id) {
+ return snd_device_name_get_hint(hint, id);
+}
+
+int AlsaWrapper::DeviceNameFreeHint(void** hints) {
+ return snd_device_name_free_hint(hints);
+}
+
int AlsaWrapper::PcmClose(snd_pcm_t* handle) {
return snd_pcm_close(handle);
}
diff --git a/media/audio/linux/alsa_wrapper.h b/media/audio/linux/alsa_wrapper.h
index d7686dd..5ab3c84 100644
--- a/media/audio/linux/alsa_wrapper.h
+++ b/media/audio/linux/alsa_wrapper.h
@@ -15,6 +15,10 @@ class AlsaWrapper {
AlsaWrapper();
virtual ~AlsaWrapper();
+ virtual int DeviceNameHint(int card, const char* iface, void*** hints);
+ virtual char* DeviceNameGetHint(const void* hint, const char* id);
+ virtual int DeviceNameFreeHint(void** hints);
+
virtual int PcmOpen(snd_pcm_t** handle, const char* name,
snd_pcm_stream_t stream, int mode);
virtual int PcmClose(snd_pcm_t* handle);
diff --git a/media/audio/linux/audio_manager_linux.cc b/media/audio/linux/audio_manager_linux.cc
index 0c4db6a..9d395e8 100644
--- a/media/audio/linux/audio_manager_linux.cc
+++ b/media/audio/linux/audio_manager_linux.cc
@@ -5,10 +5,13 @@
#include "media/audio/linux/audio_manager_linux.h"
#include "base/at_exit.h"
+#include "base/command_line.h"
#include "base/logging.h"
#include "media/audio/fake_audio_output_stream.h"
#include "media/audio/linux/alsa_output.h"
#include "media/audio/linux/alsa_wrapper.h"
+#include "media/base/media_switches.h"
+
namespace {
AudioManagerLinux* g_audio_manager = NULL;
@@ -34,15 +37,14 @@ AudioOutputStream* AudioManagerLinux::MakeAudioStream(Format format,
return NULL;
}
- // TODO(ajwong): Do we want to be able to configure the device? default
- // should work correctly for all mono/stereo, but not surround, which needs
- // surround40, surround51, etc.
- //
- // http://0pointer.de/blog/projects/guide-to-sound-apis.html
+ std::string device_name = AlsaPcmOutputStream::kAutoSelectDevice;
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAlsaDevice)) {
+ device_name = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kAlsaDevice);
+ }
AlsaPcmOutputStream* stream =
- new AlsaPcmOutputStream(AlsaPcmOutputStream::kDefaultDevice,
- format, channels, sample_rate, bits_per_sample,
- wrapper_.get(), this,
+ new AlsaPcmOutputStream(device_name, format, channels, sample_rate,
+ bits_per_sample, wrapper_.get(), this,
audio_thread_.message_loop());
AutoLock l(lock_);
diff --git a/media/audio/mac/audio_output_mac.cc b/media/audio/mac/audio_output_mac.cc
index a8f34f3..42e80a1 100644
--- a/media/audio/mac/audio_output_mac.cc
+++ b/media/audio/mac/audio_output_mac.cc
@@ -171,7 +171,7 @@ void PCMQueueOutAudioOutputStream::GetVolume(double* left_level,
// TODO(fbarchard): Switch layout when ffmpeg is updated.
namespace {
template<class Format>
-static void SwizzleLayout(Format *b, size_t filled) {
+static void SwizzleLayout(Format* b, size_t filled) {
static const int kNumSurroundChannels = 6;
Format aac[kNumSurroundChannels];
for (size_t i = 0; i < filled; i += sizeof(aac), b += kNumSurroundChannels) {
@@ -274,4 +274,3 @@ void PCMQueueOutAudioOutputStream::Start(AudioSourceCallback* callback) {
return;
}
}
-