Fix incorrect CHANNELS reporting for AC3/EC3
Fixes #313 Change-Id: I0b8d8081cda8aaa4db2ca4916bfc6721bfa3bd22
This commit is contained in:
parent
858fcd8698
commit
2ba23e1075
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2018 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include "packager/media/codecs/ac3_audio_util.h"
|
||||||
|
|
||||||
|
#include "packager/base/strings/string_number_conversions.h"
|
||||||
|
#include "packager/media/base/bit_reader.h"
|
||||||
|
#include "packager/media/base/rcheck.h"
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// ASTC Standard A/52:2012 Table 5.8 Audio Coding Mode.
|
||||||
|
const uint8_t kAc3NumChannelsTable[] = {2, 1, 2, 3, 3, 4, 4, 5};
|
||||||
|
|
||||||
|
bool ExtractAc3Data(const std::vector<uint8_t>& ac3_data,
|
||||||
|
uint8_t* audio_coding_mode,
|
||||||
|
bool* lfe_channel_on) {
|
||||||
|
BitReader bit_reader(ac3_data.data(), ac3_data.size());
|
||||||
|
|
||||||
|
// fscod: 2 bits
|
||||||
|
// bsid: 5 bits
|
||||||
|
// bsmod: 3 bits
|
||||||
|
// acmod: 3 bits
|
||||||
|
// lfeon: 1 bit
|
||||||
|
// bit_rate_code: 5 bits
|
||||||
|
RCHECK(bit_reader.SkipBits(10));
|
||||||
|
RCHECK(bit_reader.ReadBits(3, audio_coding_mode));
|
||||||
|
RCHECK(bit_reader.ReadBits(1, lfe_channel_on));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
size_t GetAc3NumChannels(const std::vector<uint8_t>& ac3_data) {
|
||||||
|
uint8_t audio_coding_mode;
|
||||||
|
bool lfe_channel_on;
|
||||||
|
if (!ExtractAc3Data(ac3_data, &audio_coding_mode, &lfe_channel_on)) {
|
||||||
|
LOG(WARNING) << "Seeing invalid AC3 data: "
|
||||||
|
<< base::HexEncode(ac3_data.data(), ac3_data.size());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return kAc3NumChannelsTable[audio_coding_mode] + (lfe_channel_on ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace shaka
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright 2018 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
//
|
||||||
|
// AC3 audio utility functions.
|
||||||
|
|
||||||
|
#ifndef PACKAGER_MEDIA_CODECS_AC3_AUDIO_UTIL_H_
|
||||||
|
#define PACKAGER_MEDIA_CODECS_AC3_AUDIO_UTIL_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
/// Parse data from AC3Specific box and calculate number of channels.
|
||||||
|
/// @return The number of channels associated with the input ac3 data on
|
||||||
|
/// success; otherwise 0 is returned.
|
||||||
|
size_t GetAc3NumChannels(const std::vector<uint8_t>& ac3_data);
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace shaka
|
||||||
|
|
||||||
|
#endif // PACKAGER_MEDIA_CODECS_AC3_AUDIO_UTIL_H_
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2018 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "packager/media/codecs/ac3_audio_util.h"
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
TEST(Ac3AudioUtilTest, ChannelTest1) {
|
||||||
|
// audio_coding_mode is 7, which is Left, Center, Right, Left surround, Right
|
||||||
|
// surround. LFE channel on.
|
||||||
|
const std::vector<uint8_t> ac3_data = {0x10, 0x3d, 0xc0};
|
||||||
|
|
||||||
|
EXPECT_EQ(6u, GetAc3NumChannels(ac3_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Ac3AudioUtilTest, ChannelTest2) {
|
||||||
|
// audio_coding_mode is 2, which is Left and Right. LFE channel off.
|
||||||
|
const std::vector<uint8_t> ac3_data = {0x10, 0x11, 0xc0};
|
||||||
|
|
||||||
|
EXPECT_EQ(2u, GetAc3NumChannels(ac3_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace shaka
|
|
@ -15,6 +15,8 @@
|
||||||
'sources': [
|
'sources': [
|
||||||
'aac_audio_specific_config.cc',
|
'aac_audio_specific_config.cc',
|
||||||
'aac_audio_specific_config.h',
|
'aac_audio_specific_config.h',
|
||||||
|
'ac3_audio_util.cc',
|
||||||
|
'ac3_audio_util.h',
|
||||||
'avc_decoder_configuration_record.cc',
|
'avc_decoder_configuration_record.cc',
|
||||||
'avc_decoder_configuration_record.h',
|
'avc_decoder_configuration_record.h',
|
||||||
'decoder_configuration_record.cc',
|
'decoder_configuration_record.cc',
|
||||||
|
@ -61,6 +63,7 @@
|
||||||
'type': '<(gtest_target_type)',
|
'type': '<(gtest_target_type)',
|
||||||
'sources': [
|
'sources': [
|
||||||
'aac_audio_specific_config_unittest.cc',
|
'aac_audio_specific_config_unittest.cc',
|
||||||
|
'ac3_audio_util_unittest.cc',
|
||||||
'avc_decoder_configuration_record_unittest.cc',
|
'avc_decoder_configuration_record_unittest.cc',
|
||||||
'ec3_audio_util_unittest.cc',
|
'ec3_audio_util_unittest.cc',
|
||||||
'es_descriptor_unittest.cc',
|
'es_descriptor_unittest.cc',
|
||||||
|
|
|
@ -4,9 +4,12 @@
|
||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
// https://developers.google.com/open-source/licenses/bsd
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
#include "packager/media/codecs/ec3_audio_util.h"
|
||||||
|
|
||||||
|
#include "packager/base/macros.h"
|
||||||
|
#include "packager/base/strings/string_number_conversions.h"
|
||||||
#include "packager/media/base/bit_reader.h"
|
#include "packager/media/base/bit_reader.h"
|
||||||
#include "packager/media/base/rcheck.h"
|
#include "packager/media/base/rcheck.h"
|
||||||
#include "packager/media/codecs/ec3_audio_util.h"
|
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
@ -49,6 +52,13 @@ enum kEC3AudioChannelMap {
|
||||||
kLFE2 = 0x2,
|
kLFE2 = 0x2,
|
||||||
kLFEScreen = 0x1
|
kLFEScreen = 0x1
|
||||||
};
|
};
|
||||||
|
// Number of channels for the channel bit above. The first entry corresponds to
|
||||||
|
// kLeft, which has one channel. All the XxxPairs bits have two channels.
|
||||||
|
const size_t kChannelCountArray[] = {
|
||||||
|
1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1, 2, 1, 1,
|
||||||
|
};
|
||||||
|
static_assert(arraysize(kChannelCountArray) == 16u,
|
||||||
|
"Channel count array should have 16 entries.");
|
||||||
|
|
||||||
// EC3 Audio coding mode map (acmod) to determine EC3 audio channel layout. The
|
// EC3 Audio coding mode map (acmod) to determine EC3 audio channel layout. The
|
||||||
// value stands for the existence of Left, Center, Right, Left surround, and
|
// value stands for the existence of Left, Center, Right, Left surround, and
|
||||||
|
@ -118,13 +128,17 @@ bool ExtractEc3Data(const std::vector<uint8_t>& ec3_data,
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data, uint32_t* channel_map) {
|
bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data,
|
||||||
|
uint32_t* channel_map) {
|
||||||
uint8_t audio_coding_mode;
|
uint8_t audio_coding_mode;
|
||||||
bool lfe_channel_on;
|
bool lfe_channel_on;
|
||||||
uint16_t dependent_substreams_layout;
|
uint16_t dependent_substreams_layout;
|
||||||
if (!ExtractEc3Data(ec3_data, &audio_coding_mode, &lfe_channel_on,
|
if (!ExtractEc3Data(ec3_data, &audio_coding_mode, &lfe_channel_on,
|
||||||
&dependent_substreams_layout))
|
&dependent_substreams_layout)) {
|
||||||
|
LOG(WARNING) << "Seeing invalid EC3 data: "
|
||||||
|
<< base::HexEncode(ec3_data.data(), ec3_data.size());
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Dependent substreams layout bit map:
|
// Dependent substreams layout bit map:
|
||||||
// Bit, Location
|
// Bit, Location
|
||||||
|
@ -151,5 +165,21 @@ bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data, uint32_t* chan
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t GetEc3NumChannels(const std::vector<uint8_t>& ec3_data) {
|
||||||
|
uint32_t channel_map;
|
||||||
|
if (!CalculateEC3ChannelMap(ec3_data, &channel_map))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
size_t num_channels = 0;
|
||||||
|
int bit = kLeft;
|
||||||
|
for (size_t channel_count : kChannelCountArray) {
|
||||||
|
if (channel_map & bit)
|
||||||
|
num_channels += channel_count;
|
||||||
|
bit >>= 1;
|
||||||
|
}
|
||||||
|
DCHECK_EQ(bit, 0);
|
||||||
|
return num_channels;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#ifndef PACKAGER_MEDIA_CODECS_EC3_AUDIO_UTIL_H_
|
#ifndef PACKAGER_MEDIA_CODECS_EC3_AUDIO_UTIL_H_
|
||||||
#define PACKAGER_MEDIA_CODECS_EC3_AUDIO_UTIL_H_
|
#define PACKAGER_MEDIA_CODECS_EC3_AUDIO_UTIL_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -21,6 +22,11 @@ namespace media {
|
||||||
bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data,
|
bool CalculateEC3ChannelMap(const std::vector<uint8_t>& ec3_data,
|
||||||
uint32_t* channel_map);
|
uint32_t* channel_map);
|
||||||
|
|
||||||
|
/// Parse data from EC3Specific box and calculate number of channels.
|
||||||
|
/// @return The number of channels associated with the input ec3 data on
|
||||||
|
/// success; otherwise 0 is returned.
|
||||||
|
size_t GetEc3NumChannels(const std::vector<uint8_t>& ec3_data);
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
||||||
|
|
|
@ -6,44 +6,43 @@
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "packager/base/macros.h"
|
|
||||||
#include "packager/media/codecs/ec3_audio_util.h"
|
#include "packager/media/codecs/ec3_audio_util.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
TEST(EC3AudioUtilTest, CalculateEC3ChannelMapTest1) {
|
TEST(EC3AudioUtilTest, ChannelTest1) {
|
||||||
// audio_coding_mode is 7, which is Left, Center, Right, Left surround, Right
|
// audio_coding_mode is 7, which is Left, Center, Right, Left surround, Right
|
||||||
// surround. No dependent substreams. LFE channel on.
|
// surround. No dependent substreams. LFE channel on.
|
||||||
const uint8_t kEc3Data[] = {0, 0, 0, 0x0f, 0};
|
const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x0f, 0};
|
||||||
|
|
||||||
uint32_t channel_map;
|
uint32_t channel_map;
|
||||||
EXPECT_TRUE(CalculateEC3ChannelMap(
|
EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map));
|
||||||
std::vector<uint8_t>(kEc3Data, kEc3Data + arraysize(kEc3Data)),
|
|
||||||
&channel_map));
|
|
||||||
EXPECT_EQ(0xF801u, channel_map);
|
EXPECT_EQ(0xF801u, channel_map);
|
||||||
|
EXPECT_EQ(6u, GetEc3NumChannels(ec3_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EC3AudioUtilTest, CalculateEC3ChannelMapTest2) {
|
TEST(EC3AudioUtilTest, ChannelTest2) {
|
||||||
// audio_coding_mode is 2, which is Left and Right. No dependent substreams.
|
// audio_coding_mode is 2, which is Left and Right. No dependent substreams.
|
||||||
// LFE channel off.
|
// LFE channel off.
|
||||||
const uint8_t kEc3Data[] = {0, 0, 0, 0x04, 0};
|
const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x04, 0};
|
||||||
|
|
||||||
uint32_t channel_map;
|
uint32_t channel_map;
|
||||||
EXPECT_TRUE(CalculateEC3ChannelMap(
|
EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map));
|
||||||
std::vector<uint8_t>(kEc3Data, kEc3Data + arraysize(kEc3Data)),
|
|
||||||
&channel_map));
|
|
||||||
EXPECT_EQ(0xA000u, channel_map);
|
EXPECT_EQ(0xA000u, channel_map);
|
||||||
|
EXPECT_EQ(2u, GetEc3NumChannels(ec3_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EC3AudioUtilTest, CalculateEC3ChannelMapTest3) {
|
TEST(EC3AudioUtilTest, ChannelTest3) {
|
||||||
// audio_coding_mode is 3, which is Left, Center, and Right. Dependent
|
// audio_coding_mode is 3, which is Left, Center, and Right. Dependent
|
||||||
// substreams layout is 0b100000011, which is Left center/ Right center pair,
|
// substreams layout is 0b100000011, which is Left center/ Right center pair,
|
||||||
// Left rear surround/ Right rear surround pair, LFE2 on. LFE channel on.
|
// Left rear surround/ Right rear surround pair, LFE2 on. LFE channel on.
|
||||||
const uint8_t kEc3Data[] = {0, 0, 0, 0x07, 0x07, 0x03};
|
const std::vector<uint8_t> ec3_data = {0, 0, 0, 0x07, 0x07, 0x03};
|
||||||
|
|
||||||
uint32_t channel_map;
|
uint32_t channel_map;
|
||||||
EXPECT_TRUE(CalculateEC3ChannelMap(
|
EXPECT_TRUE(CalculateEC3ChannelMap(ec3_data, &channel_map));
|
||||||
std::vector<uint8_t>(kEc3Data, kEc3Data + arraysize(kEc3Data)),
|
|
||||||
&channel_map));
|
|
||||||
EXPECT_EQ(0xE603u, channel_map);
|
EXPECT_EQ(0xE603u, channel_map);
|
||||||
|
EXPECT_EQ(9u, GetEc3NumChannels(ec3_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -21,7 +21,9 @@
|
||||||
#include "packager/media/base/media_sample.h"
|
#include "packager/media/base/media_sample.h"
|
||||||
#include "packager/media/base/rcheck.h"
|
#include "packager/media/base/rcheck.h"
|
||||||
#include "packager/media/base/video_stream_info.h"
|
#include "packager/media/base/video_stream_info.h"
|
||||||
|
#include "packager/media/codecs/ac3_audio_util.h"
|
||||||
#include "packager/media/codecs/avc_decoder_configuration_record.h"
|
#include "packager/media/codecs/avc_decoder_configuration_record.h"
|
||||||
|
#include "packager/media/codecs/ec3_audio_util.h"
|
||||||
#include "packager/media/codecs/es_descriptor.h"
|
#include "packager/media/codecs/es_descriptor.h"
|
||||||
#include "packager/media/codecs/hevc_decoder_configuration_record.h"
|
#include "packager/media/codecs/hevc_decoder_configuration_record.h"
|
||||||
#include "packager/media/codecs/vp_codec_configuration_record.h"
|
#include "packager/media/codecs/vp_codec_configuration_record.h"
|
||||||
|
@ -438,12 +440,12 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
||||||
break;
|
break;
|
||||||
case FOURCC_ac_3:
|
case FOURCC_ac_3:
|
||||||
codec_config = entry.dac3.data;
|
codec_config = entry.dac3.data;
|
||||||
num_channels = entry.channelcount;
|
num_channels = static_cast<uint8_t>(GetAc3NumChannels(codec_config));
|
||||||
sampling_frequency = entry.samplerate;
|
sampling_frequency = entry.samplerate;
|
||||||
break;
|
break;
|
||||||
case FOURCC_ec_3:
|
case FOURCC_ec_3:
|
||||||
codec_config = entry.dec3.data;
|
codec_config = entry.dec3.data;
|
||||||
num_channels = entry.channelcount;
|
num_channels = static_cast<uint8_t>(GetEc3NumChannels(codec_config));
|
||||||
sampling_frequency = entry.samplerate;
|
sampling_frequency = entry.samplerate;
|
||||||
break;
|
break;
|
||||||
case FOURCC_Opus:
|
case FOURCC_Opus:
|
||||||
|
|
|
@ -348,8 +348,15 @@ void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (audio_info->codec() == kCodecAC3 || audio_info->codec() == kCodecEAC3) {
|
||||||
|
// AC3 and EC3 does not fill in actual channel count and sample size in
|
||||||
|
// sample description entry. Instead, two constants are used.
|
||||||
|
audio.channelcount = 2;
|
||||||
|
audio.samplesize = 16;
|
||||||
|
} else {
|
||||||
audio.channelcount = audio_info->num_channels();
|
audio.channelcount = audio_info->num_channels();
|
||||||
audio.samplesize = audio_info->sample_bits();
|
audio.samplesize = audio_info->sample_bits();
|
||||||
|
}
|
||||||
audio.samplerate = audio_info->sampling_frequency();
|
audio.samplerate = audio_info->sampling_frequency();
|
||||||
SampleTable& sample_table = trak->media.information.sample_table;
|
SampleTable& sample_table = trak->media.information.sample_table;
|
||||||
SampleDescription& sample_description = sample_table.description;
|
SampleDescription& sample_description = sample_table.description;
|
||||||
|
|
Loading…
Reference in New Issue