chromium/media/base/mac/channel_layout_util_mac.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "media/base/mac/channel_layout_util_mac.h"

#include <memory>

#include "base/check_op.h"
#include "media/base/channel_layout.h"

namespace media {

ScopedAudioChannelLayout::ScopedAudioChannelLayout(size_t layout_size)
    : layout_(layout_size) {}

ScopedAudioChannelLayout::~ScopedAudioChannelLayout() = default;

bool AudioChannelLabelToChannel(AudioChannelLabel input_channel,
                                Channels* output_channel) {
  switch (input_channel) {
    case kAudioChannelLabel_Left:
      *output_channel = Channels::LEFT;
      break;
    case kAudioChannelLabel_Right:
      *output_channel = Channels::RIGHT;
      break;
    case kAudioChannelLabel_Center:
    case kAudioChannelLabel_Mono:
      *output_channel = Channels::CENTER;
      break;
    case kAudioChannelLabel_LFEScreen:
      *output_channel = Channels::LFE;
      break;
    case kAudioChannelLabel_RearSurroundLeft:
      *output_channel = Channels::BACK_LEFT;
      break;
    case kAudioChannelLabel_RearSurroundRight:
      *output_channel = Channels::BACK_RIGHT;
      break;
    case kAudioChannelLabel_LeftCenter:
      *output_channel = Channels::LEFT_OF_CENTER;
      break;
    case kAudioChannelLabel_RightCenter:
      *output_channel = Channels::RIGHT_OF_CENTER;
      break;
    case kAudioChannelLabel_CenterSurround:
      *output_channel = Channels::BACK_CENTER;
      break;
    case kAudioChannelLabel_LeftSurround:
      *output_channel = Channels::SIDE_LEFT;
      break;
    case kAudioChannelLabel_RightSurround:
      *output_channel = Channels::SIDE_RIGHT;
      break;
    default:
      return false;
  }
  return true;
}

AudioChannelLabel ChannelToAudioChannelLabel(Channels input_channel) {
  switch (input_channel) {
    case Channels::LEFT:
      return kAudioChannelLabel_Left;
    case Channels::RIGHT:
      return kAudioChannelLabel_Right;
    case Channels::CENTER:
      return kAudioChannelLabel_Center;
    case Channels::LFE:
      return kAudioChannelLabel_LFEScreen;
    case Channels::BACK_LEFT:
      return kAudioChannelLabel_RearSurroundLeft;
    case Channels::BACK_RIGHT:
      return kAudioChannelLabel_RearSurroundRight;
    case Channels::LEFT_OF_CENTER:
      return kAudioChannelLabel_LeftCenter;
    case Channels::RIGHT_OF_CENTER:
      return kAudioChannelLabel_RightCenter;
    case Channels::BACK_CENTER:
      return kAudioChannelLabel_CenterSurround;
    case Channels::SIDE_LEFT:
      return kAudioChannelLabel_LeftSurround;
    case Channels::SIDE_RIGHT:
      return kAudioChannelLabel_RightSurround;
  }
}

std::unique_ptr<ScopedAudioChannelLayout> ChannelLayoutToAudioChannelLayout(
    ChannelLayout input_layout,
    int input_channels) {
  CHECK_GT(input_layout, CHANNEL_LAYOUT_UNSUPPORTED);
  CHECK_GT(input_channels, 0);

  // AudioChannelLayout is structure ending in a variable length array, so we
  // can't directly allocate one.
  //
  // Code modeled after example from Apple documentation here:
  // https://developer.apple.com/library/content/qa/qa1627/_index.html
  int output_layout_size =
      offsetof(AudioChannelLayout, mChannelDescriptions[input_channels]);
  auto new_layout =
      std::make_unique<ScopedAudioChannelLayout>(output_layout_size);

  new_layout->layout()->mNumberChannelDescriptions = input_channels;
  new_layout->layout()->mChannelLayoutTag =
      kAudioChannelLayoutTag_UseChannelDescriptions;
  AudioChannelDescription* descriptions =
      new_layout->layout()->mChannelDescriptions;

  if (input_layout == CHANNEL_LAYOUT_DISCRETE) {
    // For the discrete case, mark all channels as unknown.
    for (int ch = 0; ch < input_channels; ++ch) {
      descriptions[ch].mChannelLabel = kAudioChannelLabel_Unknown;
      descriptions[ch].mChannelFlags = kAudioChannelFlags_AllOff;
    }
  } else if (input_layout == CHANNEL_LAYOUT_MONO) {
    // CoreAudio has a special label for mono.
    CHECK_EQ(input_channels, 1);
    descriptions[0].mChannelLabel = kAudioChannelLabel_Mono;
    descriptions[0].mChannelFlags = kAudioChannelFlags_AllOff;
  } else {
    for (int ch = 0; ch <= CHANNELS_MAX; ++ch) {
      const int order = ChannelOrder(input_layout, static_cast<Channels>(ch));
      if (order == -1) {
        continue;
      }
      descriptions[order].mChannelLabel =
          ChannelToAudioChannelLabel(static_cast<Channels>(ch));
      descriptions[order].mChannelFlags = kAudioChannelFlags_AllOff;
    }
  }

  return new_layout;
}

bool AudioChannelLayoutToChannelLayout(const AudioChannelLayout& input_layout,
                                       ChannelLayout* output_layout) {
  OSStatus result = noErr;
  UInt32 size = 0;
  AudioChannelFlags tag = input_layout.mChannelLayoutTag;
  if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
    result = AudioFormatGetPropertyInfo(
        kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(UInt32),
        &input_layout.mChannelBitmap, &size);
  } else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
    result =
        AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForTag,
                                   sizeof(AudioChannelLayoutTag), &tag, &size);
  }

  if (result != noErr) {
    return false;
  }

  ScopedAudioChannelLayout new_layout(size);

  if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
    result = AudioFormatGetProperty(
        kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(UInt32),
        &input_layout.mChannelBitmap, &size, new_layout.layout());
  } else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
    result = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
                                    sizeof(AudioChannelLayoutTag), &tag, &size,
                                    new_layout.layout());
  }

  if (result != noErr) {
    return false;
  }

  UInt32 channel_count = 0;
  if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
    new_layout.layout()->mChannelLayoutTag =
        kAudioChannelLayoutTag_UseChannelDescriptions;
    channel_count = new_layout.layout()->mNumberChannelDescriptions;
  } else {
    channel_count = input_layout.mNumberChannelDescriptions;
  }
  CHECK_GT(static_cast<int>(channel_count), 0);

  std::vector<Channels> channels_to_match;
  for (UInt32 i = 0; i < channel_count; i++) {
    Channels channel;
    auto channelLabel =
        tag == kAudioChannelLayoutTag_UseChannelDescriptions
            ? input_layout.mChannelDescriptions[i].mChannelLabel
            : new_layout.layout()->mChannelDescriptions[i].mChannelLabel;
    if (!AudioChannelLabelToChannel(channelLabel, &channel)) {
      return false;
    }
    channels_to_match.push_back(channel);
  }

  for (int i = 0; i <= ChannelLayout::CHANNEL_LAYOUT_MAX; i++) {
    ChannelLayout layout = static_cast<ChannelLayout>(i);
    if (static_cast<UInt32>(ChannelLayoutToChannelCount(layout)) !=
        channel_count) {
      continue;
    }

    bool matched = true;
    for (const auto& channel : channels_to_match) {
      auto channel_order = ChannelOrder(layout, channel);
      if (channel_order == -1) {
        matched = false;
        break;
      }
    }

    if (matched) {
      *output_layout = layout;
      return true;
    }
  }

  return false;
}

}  // namespace media