Index: media/audio/win/audio_low_latency_output_win.cc |
diff --git a/media/audio/win/audio_low_latency_output_win.cc b/media/audio/win/audio_low_latency_output_win.cc |
index 65ed8be35ed262630c51fe0588c21ab65b172242..2b270c179f0d3f53250b260c5e8bb899497ff7ab 100644 |
--- a/media/audio/win/audio_low_latency_output_win.cc |
+++ b/media/audio/win/audio_low_latency_output_win.cc |
@@ -21,6 +21,300 @@ using base::win::ScopedCoMem; |
namespace media { |
+typedef uint32 ChannelConfig; |
+ |
+// Ensure that the alignment of members will be on a boundary that is a |
+// multiple of 1 byte. |
+#pragma pack(push) |
+#pragma pack(1) |
+ |
+struct LayoutMono_16bit { |
+ int16 center; |
+}; |
+ |
+struct LayoutStereo_16bit { |
+ int16 left; |
+ int16 right; |
+}; |
+ |
+struct Layout5_1_16bit { |
+ int16 front_left; |
+ int16 front_right; |
+ int16 front_center; |
+ int16 low_frequency; |
+ int16 back_left; |
+ int16 back_right; |
+}; |
+ |
+struct Layout7_1_16bit { |
+ int16 front_left; |
+ int16 front_right; |
+ int16 front_center; |
+ int16 low_frequency; |
+ int16 back_left; |
+ int16 back_right; |
+ int16 side_left; |
+ int16 side_right; |
+}; |
+ |
+#pragma pack(pop) |
+ |
+// Retrieves the stream format that the audio engine uses for its internal |
+// processing/mixing of shared-mode streams. |
+static HRESULT GetMixFormat(ERole device_role, WAVEFORMATEX** device_format) { |
+ // Note that we are using the IAudioClient::GetMixFormat() API to get the |
+ // device format in this function. It is in fact possible to be "more native", |
+ // and ask the endpoint device directly for its properties. Given a reference |
+ // to the IMMDevice interface of an endpoint object, a client can obtain a |
+ // reference to the endpoint object's property store by calling the |
+ // IMMDevice::OpenPropertyStore() method. However, I have not been able to |
+ // access any valuable information using this method on my HP Z600 desktop, |
+ // hence it feels more appropriate to use the IAudioClient::GetMixFormat() |
+ // approach instead. |
+ |
+ // Calling this function only makes sense for shared mode streams, since |
+ // if the device will be opened in exclusive mode, then the application |
+ // specified format is used instead. However, the result of this method can |
+ // be useful for testing purposes so we don't DCHECK here. |
+ DLOG_IF(WARNING, WASAPIAudioOutputStream::GetShareMode() == |
+ AUDCLNT_SHAREMODE_EXCLUSIVE) << |
+ "The mixing sample rate will be ignored for exclusive-mode streams."; |
+ |
+ // It is assumed that this static method is called from a COM thread, i.e., |
+ // CoInitializeEx() is not called here again to avoid STA/MTA conflicts. |
+ ScopedComPtr<IMMDeviceEnumerator> enumerator; |
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), |
+ NULL, |
+ CLSCTX_INPROC_SERVER, |
+ __uuidof(IMMDeviceEnumerator), |
+ enumerator.ReceiveVoid()); |
+ if (FAILED(hr)) { |
+ NOTREACHED() << "error code: " << std::hex << hr; |
+ return hr; |
+ } |
+ |
+ ScopedComPtr<IMMDevice> endpoint_device; |
+ hr = enumerator->GetDefaultAudioEndpoint(eRender, |
+ device_role, |
+ endpoint_device.Receive()); |
+ if (FAILED(hr)) { |
+ // This will happen if there's no audio output device found or available |
+ // (e.g. some audio cards that have outputs will still report them as |
+ // "not found" when no speaker is plugged into the output jack). |
+ LOG(WARNING) << "No audio end point: " << std::hex << hr; |
+ return hr; |
+ } |
+ |
+ ScopedComPtr<IAudioClient> audio_client; |
+ hr = endpoint_device->Activate(__uuidof(IAudioClient), |
+ CLSCTX_INPROC_SERVER, |
+ NULL, |
+ audio_client.ReceiveVoid()); |
+ DCHECK(SUCCEEDED(hr)) << "Failed to activate device: " << std::hex << hr; |
+ if (SUCCEEDED(hr)) { |
+ hr = audio_client->GetMixFormat(device_format); |
+ DCHECK(SUCCEEDED(hr)) << "GetMixFormat: " << std::hex << hr; |
+ } |
+ |
+ return hr; |
+} |
+ |
+// Retrieves an integer mask which corresponds to the channel layout the |
+// audio engine uses for its internal processing/mixing of shared-mode |
+// streams. This mask indicates which channels are present in the multi- |
+// channel stream. The least significant bit corresponds with the Front Left |
+// speaker, the next least significant bit corresponds to the Front Right |
+// speaker, and so on, continuing in the order defined in KsMedia.h. |
+// See http://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx |
+// for more details. |
+static ChannelConfig GetChannelConfig() { |
+ // Use a WAVEFORMATEXTENSIBLE structure since it can specify both the |
+ // number of channels and the mapping of channels to speakers for |
+ // multichannel devices. |
+ base::win::ScopedCoMem<WAVEFORMATPCMEX> format_ex; |
+ HRESULT hr = S_FALSE; |
+ hr = GetMixFormat(eConsole, reinterpret_cast<WAVEFORMATEX**>(&format_ex)); |
+ if (FAILED(hr)) |
+ return 0; |
+ |
+ // The dwChannelMask member specifies which channels are present in the |
+ // multichannel stream. The least significant bit corresponds to the |
+ // front left speaker, the next least significant bit corresponds to the |
+ // front right speaker, and so on. |
+ // See http://msdn.microsoft.com/en-us/library/windows/desktop/dd757714(v=vs.85).aspx |
+ // for more details on the channel mapping. |
+ DVLOG(2) << "dwChannelMask: 0x" << std::hex << format_ex->dwChannelMask; |
+ |
+#if !defined(NDEBUG) |
+ // See http://en.wikipedia.org/wiki/Surround_sound for more details on |
+ // how to name various speaker configurations. The list below is not complete. |
+ const char* speaker_config = "Undefined"; |
+ switch (format_ex->dwChannelMask) { |
+ case KSAUDIO_SPEAKER_MONO: |
+ speaker_config = "Mono"; |
+ break; |
+ case KSAUDIO_SPEAKER_STEREO: |
+ speaker_config = "Stereo"; |
+ break; |
+ case KSAUDIO_SPEAKER_5POINT1_SURROUND: |
+ speaker_config = "5.1 surround"; |
+ break; |
+ case KSAUDIO_SPEAKER_5POINT1: |
+ speaker_config = "5.1"; |
+ break; |
+ case KSAUDIO_SPEAKER_7POINT1_SURROUND: |
+ speaker_config = "7.1 surround"; |
+ break; |
+ case KSAUDIO_SPEAKER_7POINT1: |
+ speaker_config = "7.1"; |
+ break; |
+ default: |
+ break; |
+ } |
+ DVLOG(2) << "speaker configuration: " << speaker_config; |
+#endif |
+ |
+ return static_cast<ChannelConfig>(format_ex->dwChannelMask); |
+} |
+ |
+// Converts Microsoft's channel configuration to ChannelLayout. |
+// This mapping is not perfect but the best we can do given the current |
+// ChannelLayout enumerator and the Windows-specific speaker configurations |
+// defined in ksmedia.h. Don't assume that the channel ordering in |
+// ChannelLayout is exactly the same as the Windows specific configuration. |
+// As an example: KSAUDIO_SPEAKER_7POINT1_SURROUND is mapped to |
+// CHANNEL_LAYOUT_7_1 but the positions of Back L, Back R and Side L, Side R |
+// speakers are different in these two definitions. |
+static ChannelLayout ChannelConfigToChannelLayout(ChannelConfig config) { |
+ switch (config) { |
+ case KSAUDIO_SPEAKER_DIRECTOUT: |
+ return CHANNEL_LAYOUT_NONE; |
+ case KSAUDIO_SPEAKER_MONO: |
+ return CHANNEL_LAYOUT_MONO; |
+ case KSAUDIO_SPEAKER_STEREO: |
+ return CHANNEL_LAYOUT_STEREO; |
+ case KSAUDIO_SPEAKER_QUAD: |
+ return CHANNEL_LAYOUT_QUAD; |
+ case KSAUDIO_SPEAKER_SURROUND: |
+ return CHANNEL_LAYOUT_4_0; |
+ case KSAUDIO_SPEAKER_5POINT1: |
+ return CHANNEL_LAYOUT_5_1_BACK; |
+ case KSAUDIO_SPEAKER_5POINT1_SURROUND: |
+ return CHANNEL_LAYOUT_5_1; |
+ case KSAUDIO_SPEAKER_7POINT1: |
+ return CHANNEL_LAYOUT_7_1_WIDE; |
+ case KSAUDIO_SPEAKER_7POINT1_SURROUND: |
+ return CHANNEL_LAYOUT_7_1; |
+ default: |
+ DVLOG(1) << "Unsupported channel layout: " << config; |
+ return CHANNEL_LAYOUT_UNSUPPORTED; |
+ } |
+} |
+ |
+// mono/stereo -> N.1 up-mixing where N=out_channels-1. |
+// See http://www.w3.org/TR/webaudio/#UpMix-sub for details. |
+// TODO(henrika): try to reduce the size of this function. |
+// TODO(henrika): use ChannelLayout for channel parameters. |
+// TODO(henrika): can we do this in-place by processing the samples in |
+// reverse order when sizeof(out) > sizeof(in) (upmixing)? |
+// TODO(henrika): add support for other bit-depths as well? |
+static int ChannelUpMix(void* input, |
+ void* output, |
+ int in_channels, |
+ int out_channels, |
+ size_t number_of_input_bytes, |
+ int bytes_per_sample) { |
+ DCHECK_GT(out_channels, in_channels); |
+ DCHECK_EQ(bytes_per_sample, 2); |
+ |
+ if (bytes_per_sample != 2) { |
+ LOG(ERROR) << "Only 16-bit samples are supported."; |
+ return 0; |
+ } |
+ |
+ const int kChannelRatio = out_channels / in_channels; |
+ |
+ // 1 -> 2 |
+ if (in_channels == 1 && out_channels == 2) { |
+ LayoutMono_16bit* in = reinterpret_cast<LayoutMono_16bit*>(input); |
+ LayoutStereo_16bit* out = reinterpret_cast<LayoutStereo_16bit*>(output); |
+ int number_of_input_mono_samples = (number_of_input_bytes >> 1); |
+ |
+ // Copy same input mono sample to both output channels. |
+ for (int i = 0; i < number_of_input_mono_samples; ++i) { |
+ out->left = in->center; |
+ out->right = in->center; |
+ in++; |
+ out++; |
+ } |
+ |
+ return (kChannelRatio * number_of_input_bytes); |
+ } |
+ |
+ // 1 -> 7.1 |
+ if (in_channels == 1 && out_channels == 8) { |
+ LayoutMono_16bit* in = reinterpret_cast<LayoutMono_16bit*>(input); |
+ Layout7_1_16bit* out = reinterpret_cast<Layout7_1_16bit*>(output); |
+ int number_of_input_mono_samples = (number_of_input_bytes >> 1); |
+ |
+ // Zero out all frames first. |
+ memset(out, 0, number_of_input_mono_samples * sizeof(out[0])); |
+ |
+ // Copy input sample to output center channel. |
+ for (int i = 0; i < number_of_input_mono_samples; ++i) { |
+ out->front_center = in->center; |
+ in++; |
+ out++; |
+ } |
+ |
+ return (kChannelRatio * number_of_input_bytes); |
+ } |
+ |
+ // 2 -> 5.1 |
+ if (in_channels == 2 && out_channels == 6) { |
+ LayoutStereo_16bit* in = reinterpret_cast<LayoutStereo_16bit*>(input); |
+ Layout5_1_16bit* out = reinterpret_cast<Layout5_1_16bit*>(output); |
+ int number_of_input_stereo_samples = (number_of_input_bytes >> 2); |
+ |
+ // Zero out all frames first. |
+ memset(out, 0, number_of_input_stereo_samples * sizeof(out[0])); |
+ |
+ // Copy left and right input channels to the same output channels. |
+ for (int i = 0; i < number_of_input_stereo_samples; ++i) { |
+ out->front_left = in->left; |
+ out->front_right = in->right; |
+ in++; |
+ out++; |
+ } |
+ |
+ return (kChannelRatio * number_of_input_bytes); |
+ } |
+ |
+ // 2 -> 7.1 |
+ if (in_channels == 2 && out_channels == 8) { |
+ LayoutStereo_16bit* in = reinterpret_cast<LayoutStereo_16bit*>(input); |
+ Layout7_1_16bit* out = reinterpret_cast<Layout7_1_16bit*>(output); |
+ int number_of_input_stereo_samples = (number_of_input_bytes >> 2); |
+ |
+ // Zero out all frames first. |
+ memset(out, 0, number_of_input_stereo_samples * sizeof(out[0])); |
+ |
+ // Copy left and right input channels to the same output channels. |
+ for (int i = 0; i < number_of_input_stereo_samples; ++i) { |
+ out->front_left = in->left; |
+ out->front_right = in->right; |
+ in++; |
+ out++; |
+ } |
+ |
+ return (kChannelRatio * number_of_input_bytes); |
+ } |
+ |
+ LOG(ERROR) << "Up-mixing " << in_channels << "->" |
+ << out_channels << " is not supported."; |
+ return 0; |
+} |
+ |
// static |
AUDCLNT_SHAREMODE WASAPIAudioOutputStream::GetShareMode() { |
const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); |
@@ -43,6 +337,7 @@ WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager, |
endpoint_buffer_size_frames_(0), |
device_role_(device_role), |
share_mode_(GetShareMode()), |
+ client_channel_count_(params.channels()), |
num_written_frames_(0), |
source_(NULL) { |
CHECK(com_init_.succeeded()); |
@@ -52,26 +347,54 @@ WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager, |
bool avrt_init = avrt::Initialize(); |
DCHECK(avrt_init) << "Failed to load the avrt.dll"; |
- if (share_mode() == AUDCLNT_SHAREMODE_EXCLUSIVE) { |
+ if (share_mode_ == AUDCLNT_SHAREMODE_EXCLUSIVE) { |
VLOG(1) << ">> Note that EXCLUSIVE MODE is enabled <<"; |
} |
- // Set up the desired render format specified by the client. |
- format_.nSamplesPerSec = params.sample_rate(); |
- format_.wFormatTag = WAVE_FORMAT_PCM; |
- format_.wBitsPerSample = params.bits_per_sample(); |
- format_.nChannels = params.channels(); |
- format_.nBlockAlign = (format_.wBitsPerSample / 8) * format_.nChannels; |
- format_.nAvgBytesPerSec = format_.nSamplesPerSec * format_.nBlockAlign; |
- format_.cbSize = 0; |
+ // Set up the desired render format specified by the client. We use the |
+ // WAVE_FORMAT_EXTENSIBLE structure to ensure that multiple channel ordering |
+ // and high precision data can be supported. |
+ |
+ // Begin with the WAVEFORMATEX structure that specifies the basic format. |
+ WAVEFORMATEX* format = &format_.Format; |
+ format->wFormatTag = WAVE_FORMAT_EXTENSIBLE; |
+ format->nChannels = HardwareChannelCount(); |
+ format->nSamplesPerSec = params.sample_rate(); |
+ format->wBitsPerSample = params.bits_per_sample(); |
+ format->nBlockAlign = (format->wBitsPerSample / 8) * format->nChannels; |
+ format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign; |
+ format->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); |
+ |
+ // Add the parts which are unique to WAVE_FORMAT_EXTENSIBLE. |
+ format_.Samples.wValidBitsPerSample = params.bits_per_sample(); |
+ format_.dwChannelMask = GetChannelConfig(); |
+ format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; |
// Size in bytes of each audio frame. |
- frame_size_ = format_.nBlockAlign; |
+ frame_size_ = format->nBlockAlign; |
+ |
+ // It is possible to set the number of channels in |params| to a lower value |
+ // than we use as the internal number of audio channels when the audio stream |
+ // is opened. If this mode (channel_factor_ > 1) is set, the native audio |
+ // layer will expect a larger number of channels in the interleaved audio |
+ // stream and a channel up-mix will be performed after the OnMoreData() |
+ // callback to compensate for the lower number of channels provided by the |
+ // audio source. |
+ // Example: params.channels() is 2 and endpoint_channel_count() is 8 => |
+ // the audio stream is opened up in 7.1 surround mode but the source only |
+ // provides a stereo signal as input, i.e., a stereo up-mix (2 -> 7.1) will |
+ // take place before sending the stream to the audio driver. |
+ DVLOG(1) << "Channel mixing " << client_channel_count_ << "->" |
+ << endpoint_channel_count() << " is requested."; |
+ LOG_IF(ERROR, channel_factor() < 1) |
+ << "Channel mixing " << client_channel_count_ << "->" |
+ << endpoint_channel_count() << " is not supported."; |
// Store size (in different units) of audio packets which we expect to |
// get from the audio endpoint device in each render event. |
- packet_size_frames_ = params.GetBytesPerBuffer() / format_.nBlockAlign; |
- packet_size_bytes_ = params.GetBytesPerBuffer(); |
+ packet_size_frames_ = |
+ (channel_factor() * params.GetBytesPerBuffer()) / format->nBlockAlign; |
+ packet_size_bytes_ = channel_factor() * params.GetBytesPerBuffer(); |
packet_size_ms_ = (1000.0 * packet_size_frames_) / params.sample_rate(); |
DVLOG(1) << "Number of bytes per audio frame : " << frame_size_; |
DVLOG(1) << "Number of audio frames per packet: " << packet_size_frames_; |
@@ -100,6 +423,21 @@ bool WASAPIAudioOutputStream::Open() { |
if (opened_) |
return true; |
+ // Down-mixing is currently not supported. The number of channels provided |
+ // by the audio source must be less than or equal to the number of native |
+ // channels (given by endpoint_channel_count()) which is the channel count |
+ // used when opening the default endpoint device. |
+ if (channel_factor() < 1) { |
+ LOG(ERROR) << "Channel down-mixing is not supported"; |
+ return false; |
+ } |
+ |
+ // Only 16-bit audio is supported in combination with channel up-mixing. |
+ if (channel_factor() > 1 && (format_.Format.wBitsPerSample != 16)) { |
+ LOG(ERROR) << "16-bit audio is required when channel up-mixing is active."; |
+ return false; |
+ } |
+ |
// Create an IMMDeviceEnumerator interface and obtain a reference to |
// the IMMDevice interface of the default rendering device with the |
// specified role. |
@@ -245,7 +583,7 @@ void WASAPIAudioOutputStream::Stop() { |
// If the buffers are not cleared correctly, the next call to Start() |
// would fail with AUDCLNT_E_BUFFER_ERROR at IAudioRenderClient::GetBuffer(). |
// This check is is only needed for shared-mode streams. |
- if (share_mode() == AUDCLNT_SHAREMODE_SHARED) { |
+ if (share_mode_ == AUDCLNT_SHAREMODE_SHARED) { |
UINT32 num_queued_frames = 0; |
audio_client_->GetCurrentPadding(&num_queued_frames); |
DCHECK_EQ(0u, num_queued_frames); |
@@ -293,59 +631,37 @@ void WASAPIAudioOutputStream::GetVolume(double* volume) { |
} |
// static |
-int WASAPIAudioOutputStream::HardwareSampleRate(ERole device_role) { |
- // Calling this function only makes sense for shared mode streams, since |
- // if the device will be opened in exclusive mode, then the application |
- // specified format is used instead. However, the result of this method can |
- // be useful for testing purposes so we don't DCHECK here. |
- DLOG_IF(WARNING, GetShareMode() == AUDCLNT_SHAREMODE_EXCLUSIVE) << |
- "The mixing sample rate will be ignored for exclusive-mode streams."; |
+int WASAPIAudioOutputStream::HardwareChannelCount() { |
+ // Use a WAVEFORMATEXTENSIBLE structure since it can specify both the |
+ // number of channels and the mapping of channels to speakers for |
+ // multichannel devices. |
+ base::win::ScopedCoMem<WAVEFORMATPCMEX> format_ex; |
+ HRESULT hr = GetMixFormat( |
+ eConsole, reinterpret_cast<WAVEFORMATEX**>(&format_ex)); |
+ if (FAILED(hr)) |
+ return 0; |
- // It is assumed that this static method is called from a COM thread, i.e., |
- // CoInitializeEx() is not called here again to avoid STA/MTA conflicts. |
- ScopedComPtr<IMMDeviceEnumerator> enumerator; |
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), |
- NULL, |
- CLSCTX_INPROC_SERVER, |
- __uuidof(IMMDeviceEnumerator), |
- enumerator.ReceiveVoid()); |
- if (FAILED(hr)) { |
- NOTREACHED() << "error code: " << std::hex << hr; |
- return 0.0; |
- } |
+ // Number of channels in the stream. Corresponds to the number of bits |
+ // set in the dwChannelMask. |
+ DVLOG(1) << "endpoint channels (out): " << format_ex->Format.nChannels; |
- ScopedComPtr<IMMDevice> endpoint_device; |
- hr = enumerator->GetDefaultAudioEndpoint(eRender, |
- device_role, |
- endpoint_device.Receive()); |
- if (FAILED(hr)) { |
- // This will happen if there's no audio output device found or available |
- // (e.g. some audio cards that have outputs will still report them as |
- // "not found" when no speaker is plugged into the output jack). |
- LOG(WARNING) << "No audio end point: " << std::hex << hr; |
- return 0.0; |
- } |
+ return static_cast<int>(format_ex->Format.nChannels); |
+} |
- ScopedComPtr<IAudioClient> audio_client; |
- hr = endpoint_device->Activate(__uuidof(IAudioClient), |
- CLSCTX_INPROC_SERVER, |
- NULL, |
- audio_client.ReceiveVoid()); |
- if (FAILED(hr)) { |
- NOTREACHED() << "error code: " << std::hex << hr; |
- return 0.0; |
- } |
+// static |
+ChannelLayout WASAPIAudioOutputStream::HardwareChannelLayout() { |
+ return ChannelConfigToChannelLayout(GetChannelConfig()); |
+} |
- // Retrieve the stream format that the audio engine uses for its internal |
- // processing of shared-mode streams. |
- base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format; |
- hr = audio_client->GetMixFormat(&audio_engine_mix_format); |
- if (FAILED(hr)) { |
- NOTREACHED() << "error code: " << std::hex << hr; |
- return 0.0; |
- } |
+// static |
+int WASAPIAudioOutputStream::HardwareSampleRate(ERole device_role) { |
+ base::win::ScopedCoMem<WAVEFORMATEX> format; |
+ HRESULT hr = GetMixFormat(device_role, &format); |
+ if (FAILED(hr)) |
+ return 0; |
- return static_cast<int>(audio_engine_mix_format->nSamplesPerSec); |
+ DVLOG(2) << "nSamplesPerSec: " << format->nSamplesPerSec; |
+ return static_cast<int>(format->nSamplesPerSec); |
} |
void WASAPIAudioOutputStream::Run() { |
@@ -426,7 +742,7 @@ void WASAPIAudioOutputStream::Run() { |
// engine has not yet read from the buffer. |
size_t num_available_frames = 0; |
- if (share_mode() == AUDCLNT_SHAREMODE_SHARED) { |
+ if (share_mode_ == AUDCLNT_SHAREMODE_SHARED) { |
// Get the padding value which represents the amount of rendering |
// data that is queued up to play in the endpoint buffer. |
hr = audio_client_->GetCurrentPadding(&num_queued_frames); |
@@ -478,7 +794,7 @@ void WASAPIAudioOutputStream::Run() { |
if (SUCCEEDED(hr)) { |
// Stream position of the sample that is currently playing |
// through the speaker. |
- double pos_sample_playing_frames = format_.nSamplesPerSec * |
+ double pos_sample_playing_frames = format_.Format.nSamplesPerSec * |
(static_cast<double>(position) / device_frequency); |
// Stream position of the last sample written to the endpoint |
@@ -499,15 +815,42 @@ void WASAPIAudioOutputStream::Run() { |
// time stamp can be used at the client side to compensate for |
// the delay between the usage of the delay value and the time |
// of generation. |
- uint32 num_filled_bytes = source_->OnMoreData( |
- audio_data, packet_size_bytes_, |
- AudioBuffersState(0, audio_delay_bytes)); |
+ |
+ uint32 num_filled_bytes = 0; |
+ const int bytes_per_sample = format_.Format.wBitsPerSample >> 3; |
+ |
+ if (channel_factor() == 1) { |
+ // Case I: no up-mixing. |
+ num_filled_bytes = source_->OnMoreData( |
+ audio_data, packet_size_bytes_, |
+ AudioBuffersState(0, audio_delay_bytes)); |
+ } else { |
+ // Case II: up-mixing. |
+ const int audio_source_size_bytes = |
+ packet_size_bytes_ / channel_factor(); |
+ scoped_array<uint8> buffer; |
+ buffer.reset(new uint8[audio_source_size_bytes]); |
+ |
+ num_filled_bytes = source_->OnMoreData( |
+ buffer.get(), audio_source_size_bytes, |
+ AudioBuffersState(0, audio_delay_bytes)); |
+ |
+ // Do channel up-mixing on 16-bit PCM samples. |
+ num_filled_bytes = ChannelUpMix(buffer.get(), |
+ &audio_data[0], |
+ client_channel_count_, |
+ endpoint_channel_count(), |
+ num_filled_bytes, |
+ bytes_per_sample); |
+ } |
// Perform in-place, software-volume adjustments. |
+ // TODO(henrika): it is possible to adjust the volume in the |
+ // ChannelUpMix() function. |
media::AdjustVolume(audio_data, |
num_filled_bytes, |
- format_.nChannels, |
- format_.wBitsPerSample >> 3, |
+ endpoint_channel_count(), |
+ bytes_per_sample, |
volume_); |
// Zero out the part of the packet which has not been filled by |
@@ -515,7 +858,7 @@ void WASAPIAudioOutputStream::Run() { |
// situation. |
if (num_filled_bytes < packet_size_bytes_) { |
memset(&audio_data[num_filled_bytes], 0, |
- (packet_size_bytes_ - num_filled_bytes)); |
+ (packet_size_bytes_ - num_filled_bytes)); |
} |
// Release the buffer space acquired in the GetBuffer() call. |
@@ -558,11 +901,11 @@ HRESULT WASAPIAudioOutputStream::SetRenderDevice() { |
ScopedComPtr<IMMDevice> endpoint_device; |
// Create the IMMDeviceEnumerator interface. |
- HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), |
- NULL, |
- CLSCTX_INPROC_SERVER, |
- __uuidof(IMMDeviceEnumerator), |
- device_enumerator.ReceiveVoid()); |
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), |
+ NULL, |
+ CLSCTX_INPROC_SERVER, |
+ __uuidof(IMMDeviceEnumerator), |
+ device_enumerator.ReceiveVoid()); |
if (SUCCEEDED(hr)) { |
// Retrieve the default render audio endpoint for the specified role. |
// Note that, in Windows Vista, the MMDevice API supports device roles |
@@ -605,7 +948,8 @@ HRESULT WASAPIAudioOutputStream::ActivateRenderDevice() { |
// Retrieve the stream format that the audio engine uses for its internal |
// processing/mixing of shared-mode streams. |
audio_engine_mix_format_.Reset(NULL); |
- hr = audio_client->GetMixFormat(&audio_engine_mix_format_); |
+ hr = audio_client->GetMixFormat( |
+ reinterpret_cast<WAVEFORMATEX**>(&audio_engine_mix_format_)); |
if (SUCCEEDED(hr)) { |
audio_client_ = audio_client; |
@@ -622,10 +966,10 @@ bool WASAPIAudioOutputStream::DesiredFormatIsSupported() { |
// which is stored in the |audio_engine_mix_format_| member and it is also |
// possible to receive a proposed (closest) format if the current format is |
// not supported. |
- base::win::ScopedCoMem<WAVEFORMATEX> closest_match; |
- HRESULT hr = audio_client_->IsFormatSupported(share_mode(), |
- &format_, |
- &closest_match); |
+ base::win::ScopedCoMem<WAVEFORMATEXTENSIBLE> closest_match; |
+ HRESULT hr = audio_client_->IsFormatSupported( |
+ share_mode_, reinterpret_cast<WAVEFORMATEX*>(&format_), |
+ reinterpret_cast<WAVEFORMATEX**>(&closest_match)); |
// This log can only be triggered for shared mode. |
DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported " |
@@ -633,10 +977,10 @@ bool WASAPIAudioOutputStream::DesiredFormatIsSupported() { |
// This log can be triggered both for shared and exclusive modes. |
DLOG_IF(ERROR, hr == AUDCLNT_E_UNSUPPORTED_FORMAT) << "Unsupported format."; |
if (hr == S_FALSE) { |
- DVLOG(1) << "wFormatTag : " << closest_match->wFormatTag; |
- DVLOG(1) << "nChannels : " << closest_match->nChannels; |
- DVLOG(1) << "nSamplesPerSec: " << closest_match->nSamplesPerSec; |
- DVLOG(1) << "wBitsPerSample: " << closest_match->wBitsPerSample; |
+ DVLOG(1) << "wFormatTag : " << closest_match->Format.wFormatTag; |
+ DVLOG(1) << "nChannels : " << closest_match->Format.nChannels; |
+ DVLOG(1) << "nSamplesPerSec: " << closest_match->Format.nSamplesPerSec; |
+ DVLOG(1) << "wBitsPerSample: " << closest_match->Format.wBitsPerSample; |
} |
return (hr == S_OK); |
@@ -678,7 +1022,7 @@ HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() { |
// Perform different initialization depending on if the device shall be |
// opened in shared mode or in exclusive mode. |
- hr = (share_mode() == AUDCLNT_SHAREMODE_SHARED) ? |
+ hr = (share_mode_ == AUDCLNT_SHAREMODE_SHARED) ? |
SharedModeInitialization() : ExclusiveModeInitialization(); |
if (FAILED(hr)) { |
LOG(WARNING) << "IAudioClient::Initialize() failed: " << std::hex << hr; |
@@ -698,7 +1042,7 @@ HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() { |
// The buffer scheme for exclusive mode streams is not designed for max |
// flexibility. We only allow a "perfect match" between the packet size set |
// by the user and the actual endpoint buffer size. |
- if (share_mode() == AUDCLNT_SHAREMODE_EXCLUSIVE && |
+ if (share_mode_ == AUDCLNT_SHAREMODE_EXCLUSIVE && |
endpoint_buffer_size_frames_ != packet_size_frames_) { |
hr = AUDCLNT_E_INVALID_SIZE; |
DLOG(ERROR) << "AUDCLNT_E_INVALID_SIZE"; |
@@ -721,17 +1065,17 @@ HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() { |
} |
HRESULT WASAPIAudioOutputStream::SharedModeInitialization() { |
- DCHECK_EQ(share_mode(), AUDCLNT_SHAREMODE_SHARED); |
+ DCHECK_EQ(share_mode_, AUDCLNT_SHAREMODE_SHARED); |
// TODO(henrika): this buffer scheme is still under development. |
// The exact details are yet to be determined based on tests with different |
// audio clients. |
int glitch_free_buffer_size_ms = static_cast<int>(packet_size_ms_ + 0.5); |
- if (audio_engine_mix_format_->nSamplesPerSec == 48000) { |
+ if (audio_engine_mix_format_->Format.nSamplesPerSec == 48000) { |
// Initial tests have shown that we have to add 10 ms extra to |
// ensure that we don't run empty for any packet size. |
glitch_free_buffer_size_ms += 10; |
- } else if (audio_engine_mix_format_->nSamplesPerSec == 44100) { |
+ } else if (audio_engine_mix_format_->Format.nSamplesPerSec == 44100) { |
// Initial tests have shown that we have to add 20 ms extra to |
// ensure that we don't run empty for any packet size. |
glitch_free_buffer_size_ms += 20; |
@@ -750,20 +1094,21 @@ HRESULT WASAPIAudioOutputStream::SharedModeInitialization() { |
// If we requests a buffer size that is smaller than the audio engine's |
// minimum required buffer size, the method sets the buffer size to this |
// minimum buffer size rather than to the buffer size requested. |
- HRESULT hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED, |
- AUDCLNT_STREAMFLAGS_EVENTCALLBACK | |
- AUDCLNT_STREAMFLAGS_NOPERSIST, |
- requested_buffer_duration, |
- 0, |
- &format_, |
- NULL); |
+ HRESULT hr = S_FALSE; |
+ hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED, |
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK | |
+ AUDCLNT_STREAMFLAGS_NOPERSIST, |
+ requested_buffer_duration, |
+ 0, |
+ reinterpret_cast<WAVEFORMATEX*>(&format_), |
+ NULL); |
return hr; |
} |
HRESULT WASAPIAudioOutputStream::ExclusiveModeInitialization() { |
- DCHECK_EQ(share_mode(), AUDCLNT_SHAREMODE_EXCLUSIVE); |
+ DCHECK_EQ(share_mode_, AUDCLNT_SHAREMODE_EXCLUSIVE); |
- float f = (1000.0 * packet_size_frames_) / format_.nSamplesPerSec; |
+ float f = (1000.0 * packet_size_frames_) / format_.Format.nSamplesPerSec; |
REFERENCE_TIME requested_buffer_duration = |
static_cast<REFERENCE_TIME>(f * 10000.0 + 0.5); |
@@ -775,13 +1120,14 @@ HRESULT WASAPIAudioOutputStream::ExclusiveModeInitialization() { |
// is equal in duration to the value of the hnsBufferDuration parameter. |
// Following the Initialize call for a rendering stream, the caller should |
// fill the first of the two buffers before starting the stream. |
- HRESULT hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, |
- AUDCLNT_STREAMFLAGS_EVENTCALLBACK | |
- AUDCLNT_STREAMFLAGS_NOPERSIST, |
- requested_buffer_duration, |
- requested_buffer_duration, |
- &format_, |
- NULL); |
+ HRESULT hr = S_FALSE; |
+ hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, |
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK | |
+ AUDCLNT_STREAMFLAGS_NOPERSIST, |
+ requested_buffer_duration, |
+ requested_buffer_duration, |
+ reinterpret_cast<WAVEFORMATEX*>(&format_), |
+ NULL); |
if (FAILED(hr)) { |
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { |
LOG(ERROR) << "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED"; |
@@ -794,7 +1140,8 @@ HRESULT WASAPIAudioOutputStream::ExclusiveModeInitialization() { |
// Calculate new aligned periodicity. Each unit of reference time |
// is 100 nanoseconds. |
REFERENCE_TIME aligned_buffer_duration = static_cast<REFERENCE_TIME>( |
- (10000000.0 * aligned_buffer_size / format_.nSamplesPerSec) + 0.5); |
+ (10000000.0 * aligned_buffer_size / format_.Format.nSamplesPerSec) |
+ + 0.5); |
// It is possible to re-activate and re-initialize the audio client |
// at this stage but we bail out with an error code instead and |
@@ -834,7 +1181,7 @@ HRESULT WASAPIAudioOutputStream::QueryInterface(REFIID iid, void** object) { |
} |
STDMETHODIMP WASAPIAudioOutputStream::OnDeviceStateChanged(LPCWSTR device_id, |
- DWORD new_state) { |
+ DWORD new_state) { |
#ifndef NDEBUG |
std::string device_name = GetDeviceName(device_id); |
std::string device_state; |
@@ -862,8 +1209,8 @@ STDMETHODIMP WASAPIAudioOutputStream::OnDeviceStateChanged(LPCWSTR device_id, |
return S_OK; |
} |
-HRESULT WASAPIAudioOutputStream::OnDefaultDeviceChanged(EDataFlow flow, |
- ERole role, LPCWSTR new_default_device_id) { |
+HRESULT WASAPIAudioOutputStream::OnDefaultDeviceChanged( |
+ EDataFlow flow, ERole role, LPCWSTR new_default_device_id) { |
if (new_default_device_id == NULL) { |
// The user has removed or disabled the default device for our |
// particular role, and no other device is available to take that role. |