Bug 1432779 - P11. Make cubeb_mixer creation infallible. r?padenot
Rather than ignore nonsensical layouts, we attempt to play it according to the stream channels count instead. The audio data will be played as-is, dropping the extra channels or inserting silence where needed.
MozReview-Commit-ID: 7bygAJMu93Z
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -82,16 +82,17 @@ struct cubeb {
void * collection_changed_user_ptr = nullptr;
/* Differentiate input from output devices. */
cubeb_device_type collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN;
vector<AudioObjectID> devtype_device_array;
// The queue is asynchronously deallocated once all references to it are released
dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
// Current used channel layout
atomic<cubeb_channel_layout> layout{ CUBEB_LAYOUT_UNDEFINED };
+ uint32_t channels = 0;
};
static unique_ptr<AudioChannelLayout, decltype(&free)>
make_sized_audio_channel_layout(size_t sz)
{
assert(sz >= sizeof(AudioChannelLayout));
AudioChannelLayout * acl = reinterpret_cast<AudioChannelLayout *>(calloc(1, sz));
assert(acl); // Assert the allocation works.
@@ -1311,26 +1312,28 @@ audio_stream_desc_init(AudioStreamBasicD
ss->mFramesPerPacket = 1;
ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket;
ss->mReserved = 0;
return CUBEB_OK;
}
-bool
+void
audiounit_init_mixer(cubeb_stream * stm)
{
// We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio
// data, it silently drop the channels so we need to remix the
// audio data by ourselves to keep all the information.
stm->mixer.reset(cubeb_mixer_create(stm->output_stream_params.format,
+ stm->output_stream_params.channels,
stm->output_stream_params.layout,
+ stm->context->channels,
stm->context->layout));
- return static_cast<bool>(stm->mixer);
+ assert(stm->mixer);
}
static int
audiounit_set_channel_layout(AudioUnit unit,
io_side side,
cubeb_channel_layout layout)
{
if (side != OUTPUT) {
@@ -2280,38 +2283,32 @@ audiounit_configure_output(cubeb_stream
&output_hw_desc,
&size);
if (r != noErr) {
LOG("AudioUnitGetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);
return CUBEB_ERROR;
}
stm->output_hw_rate = output_hw_desc.mSampleRate;
LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
-
- if (stm->output_stream_params.layout != CUBEB_LAYOUT_UNDEFINED) {
- audiounit_layout_init(stm, OUTPUT);
- if (stm->context->layout != CUBEB_LAYOUT_UNDEFINED &&
- stm->context->layout != stm->output_stream_params.layout) {
- LOG("Incompatible channel layouts detected, setting up remixer");
- if (audiounit_init_mixer(stm)) {
- // We will be remixing the data before it reaches the output device.
- // We need to adjust the number of channels and other
- // AudioStreamDescription details.
- stm->output_desc.mChannelsPerFrame =
- cubeb_channel_layout_nb_channels(stm->context->layout);
- stm->output_desc.mBytesPerFrame =
- (stm->output_desc.mBitsPerChannel / 8) *
- stm->output_desc.mChannelsPerFrame;
- stm->output_desc.mBytesPerPacket =
- stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket;
- }
- }
- if (!stm->mixer) {
- LOG("Failed to setup remixer for layout %u", stm->output_stream_params.layout);
- }
+ stm->context->channels = output_hw_desc.mChannelsPerFrame;
+
+ // Set the input layout to match the output device layout.
+ audiounit_layout_init(stm, OUTPUT);
+ if (stm->context->channels != stm->output_stream_params.channels ||
+ stm->context->layout != stm->output_stream_params.layout) {
+ LOG("Incompatible channel layouts detected, setting up remixer");
+ audiounit_init_mixer(stm);
+ // We will be remixing the data before it reaches the output device.
+ // We need to adjust the number of channels and other
+ // AudioStreamDescription details.
+ stm->output_desc.mChannelsPerFrame = stm->context->channels;
+ stm->output_desc.mBytesPerFrame = (stm->output_desc.mBitsPerChannel / 8) *
+ stm->output_desc.mChannelsPerFrame;
+ stm->output_desc.mBytesPerPacket =
+ stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket;
} else {
stm->mixer = nullptr;
}
r = AudioUnitSetProperty(stm->output_unit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
AU_OUT_BUS,
--- a/media/libcubeb/src/cubeb_mixer.cpp
+++ b/media/libcubeb/src/cubeb_mixer.cpp
@@ -73,24 +73,31 @@ unsigned int cubeb_channel_layout_nb_cha
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x + (x >> 4)) & 0x0F0F0F0F;
x += x >> 8;
return (x + (x >> 16)) & 0x3F;
#endif
}
struct MixerContext {
MixerContext(cubeb_sample_format f,
+ uint32_t in_channels,
cubeb_channel_layout in,
+ uint32_t out_channels,
cubeb_channel_layout out)
: _format(f)
, _in_ch_layout(in)
, _out_ch_layout(out)
- , _in_ch_count(cubeb_channel_layout_nb_channels(in))
- , _out_ch_count(cubeb_channel_layout_nb_channels(out))
+ , _in_ch_count(in_channels)
+ , _out_ch_count(out_channels)
{
+ if (in_channels != cubeb_channel_layout_nb_channels(in) ||
+ out_channels != cubeb_channel_layout_nb_channels(out)) {
+ // Mismatch between channels and layout, aborting.
+ return;
+ }
_valid = init() >= 0;
}
static bool even(cubeb_channel_layout layout)
{
if (!layout) {
return true;
}
@@ -488,46 +495,85 @@ static int rematrix(const MixerContext *
}
}
return 0;
}
struct cubeb_mixer
{
cubeb_mixer(cubeb_sample_format format,
+ uint32_t in_channels,
cubeb_channel_layout in_layout,
+ uint32_t out_channels,
cubeb_channel_layout out_layout)
- : _context(format, in_layout, out_layout)
+ : _context(format, in_channels, in_layout, out_channels, out_layout)
+ {
+ }
+
+ template<typename T>
+ void copy_and_trunc(unsigned long frames,
+ const T* input_buffer,
+ T* output_buffer) const
{
+ if (_context._in_ch_count <= _context._out_ch_count) {
+ // Not enough channels to copy, fill the gaps with silence.
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._in_ch_count);
+ output_buffer += _context._in_ch_count;
+ input_buffer += _context._in_ch_count;
+ PodZero(output_buffer, _context._out_ch_count - _context._in_ch_count);
+ output_buffer += _context._out_ch_count - _context._in_ch_count;
+ }
+ } else {
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._out_ch_count);
+ output_buffer += _context._out_ch_count;
+ input_buffer += _context._in_ch_count;
+ }
+ }
}
int mix(unsigned long frames,
void * input_buffer,
unsigned long input_buffer_size,
void * output_buffer,
unsigned long output_buffer_size) const
{
- if (!valid()) {
- return -1;
- }
- if (frames <= 0) {
+ if (frames <= 0 || _context._out_ch_count == 0) {
return 0;
}
// Check if output buffer is of sufficient size.
size_t size_read_needed =
frames * _context._in_ch_count * cubeb_sample_size(_context._format);
if (input_buffer_size < size_read_needed) {
// We don't have enough data to read!
return -1;
}
if (output_buffer_size * _context._in_ch_count <
size_read_needed * _context._out_ch_count) {
return -1;
}
+
+ if (!valid()) {
+ // The channel layouts were invalid or unsupported, instead we will simply
+ // either drop the extra channels, or fill with silence the missing ones
+ if (_context._format == CUBEB_SAMPLE_FLOAT32NE) {
+ copy_and_trunc(frames,
+ static_cast<const float*>(input_buffer),
+ static_cast<float*>(output_buffer));
+ } else {
+ assert(_context._format == CUBEB_SAMPLE_S16NE);
+ copy_and_trunc(frames,
+ static_cast<const int16_t*>(input_buffer),
+ static_cast<int16_t*>(output_buffer));
+ }
+ return 0;
+ }
+
switch (_context._format)
{
case CUBEB_SAMPLE_FLOAT32NE: {
auto f = [](float x) { return x; };
return rematrix(&_context,
static_cast<float*>(output_buffer),
static_cast<const float*>(input_buffer),
_context._matrix_flt,
@@ -559,34 +605,36 @@ struct cubeb_mixer
f,
frames);
}
break;
default:
assert(false);
break;
}
+
+ return -1;
}
+ // Return false if any of the input or ouput layout were invalid.
bool valid() const { return _context._valid; }
virtual ~cubeb_mixer(){};
MixerContext _context;
};
cubeb_mixer* cubeb_mixer_create(cubeb_sample_format format,
+ uint32_t in_channels,
cubeb_channel_layout in_layout,
+ uint32_t out_channels,
cubeb_channel_layout out_layout)
{
- if (!MixerContext::sane_layout(in_layout) ||
- !MixerContext::sane_layout(out_layout)) {
- return nullptr;
- }
- return new cubeb_mixer(format, in_layout, out_layout);
+ return new cubeb_mixer(
+ format, in_channels, in_layout, out_channels, out_layout);
}
void cubeb_mixer_destroy(cubeb_mixer * mixer)
{
delete mixer;
}
int cubeb_mixer_mix(cubeb_mixer * mixer,
--- a/media/libcubeb/src/cubeb_mixer.h
+++ b/media/libcubeb/src/cubeb_mixer.h
@@ -11,17 +11,19 @@
#include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params.
#if defined(__cplusplus)
extern "C" {
#endif
typedef struct cubeb_mixer cubeb_mixer;
cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format,
+ uint32_t in_channels,
cubeb_channel_layout in_layout,
+ uint32_t out_channels,
cubeb_channel_layout out_layout);
void cubeb_mixer_destroy(cubeb_mixer * mixer);
int cubeb_mixer_mix(cubeb_mixer * mixer,
unsigned long frames,
void * input_buffer,
unsigned long input_buffer_size,
void * output_buffer,
unsigned long output_buffer_size);
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -1745,24 +1745,24 @@ int setup_wasapi_stream(cubeb_stream * s
}
// Create mixer.
if (has_output(stm) && stm->output_mix_params.layout != stm->output_stream_params.layout) {
if (stm->output_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) {
LOG("Stream using undefined layout! Any mixing may be unpredictable!\n");
}
stm->mixer.reset(cubeb_mixer_create(stm->output_stream_params.format,
+ stm->output_stream_params.channels,
stm->output_stream_params.layout,
+ stm->output_mix_params.channels,
stm->output_mix_params.layout));
-
+ assert(stm->mixer);
// Input is up/down mixed when depacketized in get_input_buffer.
- if (stm->mixer) {
- stm->mix_buffer.resize(
- frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count));
- }
+ stm->mix_buffer.resize(
+ frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count));
}
return CUBEB_OK;
}
int
wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
char const * stream_name,
@@ -1795,19 +1795,17 @@ wasapi_stream_init(cubeb * context, cube
stm->user_ptr = user_ptr;
if (input_stream_params) {
stm->input_stream_params = *input_stream_params;
stm->input_device = utf8_to_wstr(reinterpret_cast<char const *>(input_device));
}
if (output_stream_params) {
stm->output_stream_params = *output_stream_params;
stm->output_device = utf8_to_wstr(reinterpret_cast<char const *>(output_device));
- // Make sure the layout matches the channel count.
- XASSERT(stm->output_stream_params.layout == CUBEB_LAYOUT_UNDEFINED ||
- stm->output_stream_params.channels == cubeb_channel_layout_nb_channels(stm->output_stream_params.layout));
+ XASSERT(stm->output_stream_params.layout == CUBEB_LAYOUT_UNDEFINED);
}
switch (output_stream_params ? output_stream_params->format : input_stream_params->format) {
case CUBEB_SAMPLE_S16NE:
stm->bytes_per_sample = sizeof(short);
stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM;
stm->linear_input_buffer.reset(new auto_array_wrapper_impl<short>);
break;